How many times load happens inside the source code of compare_exchange method in Rust?

I do not understand anything reading the source code of this method

I think the load method executes only once to load the current value of the data. Then it compares with the new one, if it matches the given current one, then the success case is executed, otherwise the failure case.

But the doc for the compare_exchange() method mentions this on its doc:

Using Acquire as success ordering makes the store part of this operation Relaxed, and using Release makes the successful load Relaxed

From this wording, it seems the in success case loads happen multiple times.

Interesting, neither the type nor the module level docs actually explicitly state the defining feature of atomics, that is, every operation on them is "atomic" in the sense that it happens all at once ("atom" meaning indivisible - the Greeks not having particle accelerators and all)

In the case of compare_exchange, it's not reading the current value then comparing, then writing - it does all three at once with a single CPU instruction, eg on x86 it's CMPXCHG.

The language around Relaxed, etc is referring to the memory model, which determines how the compiler and CPU is permitted to reorder other instructions relative to the atomic instruction, for example you don't want it to move operations on the content of a mutex either before the lock, so you use Acquire for that, nor after the unlock, so you use Release.

This is a very brief summary! Jon Gjenset's video on atomics is excellent and thorough... and over two hours long.

3 Likes

The hint may be in compare_exchange_weak() — it can fail, so I presume compare_exchange() reserves the right to be implemented as a retry loop over compare_exchange_weak().

1 Like

Technically, compare_exchange can not be implemented in terms of compare_exchange_weak, since the latter does not provide a way to distinguish between regular and spurious failures (e.g. such emulation can be problematic if current is equal to new). But the compiler indeed can generate retry loops on architectures with LR/SC instructions like RISC-V:

2 Likes

Obligatory mention of Mara Bos' Rust Atomics and Locks. It's a great resource for learning this stuff, especially if you are new to doing concurrency things.

For your case with compare_exchange, see Chapter 2.3.

4 Likes

Continuing on the example @simonbuchan mentioned, on the x86 architecture the CMPXCHG instruction, based on the Intel 64 and IA-32 Developers Manual (Page 804 in the PDF), there should only be one load in either the success or failure scenarios

If the two values are equal, the second operand (source operand) is loaded into the destination operand. Otherwise, the destination operand is loaded into the AL, AX, EAX or RAX register. RAX register is available only in 64-bit mode.

Strictly speaking, based on the pseudo code (replica of the same Intel Manual for ease of hyperlinking) for a potential implementation of CMPXCHG, there can be multiple "loads" in the sense of loading data from memory addresses into registers (e.g. TEMP := DEST and DEST := SRC are both memory loads), but the developer's manual quoted refers to a single memory load which serves as the synchronization point for memory ordering. As the manual mentions, the CMPXCHG instruction can be prefixed with LOCK which makes the operation atomic.

1 Like

Thanks. It is a good book. I have been reading this book for a few weeks. While reading chapter three of the book I stumbled upon this question. The book gave a simple overview of the compare_exchange method which is really excellent.