Live data from Hacker News

Rust Atomics and Locks: Low-Level Concurrency in Practice

marabos.nl

41–48 of 48 posts

Re: Rust Atomics and Locks: Low-Level Concurrency in Practice

#41

Earlier quoted context omitted.

Can you provide any extra information about what is unable to be expressed? I’d like to look into what sort of constructs could be useful in this context.

The keyword to search for is "memory_order_consume", which was C++11's proposed solution to the problem that turned out to not work in practice. Here are some of the C++ WG documents describing the issues: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p00... https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p01... https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p04... https://www.open-std.o…

Memory_order_consume was added to the C/C++ memory model to support architectures like the Alpha, where indirecting through memory (which includes array indexing, etc.) need not create a true happens_before relationship. So you can have things like:

Assume: a = 1, p = &a, b = 0

  Thr. 1         | Thr. 2
  b = 1          |
  memory_barrier | i = *p
  p = &b         |
The result can be i = 0

Even though b=1 happens "before" p=&b within Thread 1, it is seen as happening after it by Thread 2. To overcome this, Thread 2 needs a barrier between the load of p from memory and the indirect load that assigns to i. Practically no other architectures have that quirk, and it only happens on Alpha because of its two separate cache banks.

Unfortunately, it has turned out to be infeasible to give memory_order_consume a proper weak semantics setting it apart from memory_order_acquire, given e.g. how common it is for indirections to be altered by compiler optimizations. So Ordering::Consume does not, as of yet, exist in Rust.

Re: Rust Atomics and Locks: Low-Level Concurrency in Practice

#42
It is exceptionally good book about very hard topics.

I'm programming high-performance tasks in Java for 10+ years, and Java was first commonly-used language with formal memory model, as far as I know. C/C++/Rust memory models are modeled after Java's one.

There was no new material for me, but all material is very well organized, explained, and presented. This is very hard material, there are a lot of tutorials about JMM and most of them are worse than this book.

Other good property of this book is density of information. Many IT-related books now contains unneeded repetitions and artificial bloat to increase page count. Each page of this book is useful!

This book will be exceptionally useful not only for any Rust programmer, but, also for any non-Rust programmer who uses language with same or similar memory models, including C, C++ and Java programmers.

5 stars out of 5, recommended for every programmer who want to understand complex mechanics of memory models and high-performance concurrency of modern languages.

Re: Rust Atomics and Locks: Low-Level Concurrency in Practice

#43
post #34

I'm interested in the concept of concurrency, but is there anywhere that it would be useful outside of scientific applications and operating systems? It feels like a powerful paradigm to master but I'm just not sure it's worth the investment for the average developer.

Most web servers / apps. Just think about how you would test a simple register endpoint for example.

Re: Rust Atomics and Locks: Low-Level Concurrency in Practice

#44

Earlier quoted context omitted.

The keyword to search for is "memory_order_consume", which was C++11's proposed solution to the problem that turned out to not work in practice. Here are some of the C++ WG documents describing the issues: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p00... https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p01... https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p04... https://www.open-std.o…

Memory_order_consume was added to the C/C++ memory model to support architectures like the Alpha, where indirecting through memory (which includes array indexing, etc.) need not create a true happens_before relationship. So you can have things like: Assume: a = 1, p = &a, b = 0 Thr. 1 | Thr. 2 b = 1 | memory_barrier | i = *p p = &b | The result can be i = 0 Even though b=1 happens "before" p=&b within Thread 1, it is…

The problem of the CPU reordering things only exists on architectures like Alpha. However, there's still the problem of the compiler reordering things (which exists on every architecture), and memory_order_consume was intended to solve both. As you can see from the original rationale document from 2010, this RCU use-case was one of the main motivations:

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1525.htm

Re: Rust Atomics and Locks: Low-Level Concurrency in Practice

#45

Earlier quoted context omitted.

> How on earth are you doing concurrency without touching these two key primitives at some point? using one of the many higher level concepts/frameworks available. - actor model - fork join - CSP etc.

Someone has to implement those frameworks, and those people need to understand pretty much everything covered by this book :)

They predate rust.

Re: Rust Atomics and Locks: Low-Level Concurrency in Practice

#46
post #10

Earlier quoted context omitted.

> Then there are those dyed-in-the-wool non-Rust developers who would prefer to implement Rust's concurrency-related safety mechanisms in their own favorite language. The book's called "atomics and locks". What language doesn't already have those? If I'm touching those, doesn't that mean I'm already trying to reimplement my own concurrency-safety mechanisms? Howabout Rust implements my favourite concurrency-related s…

Maybe the book isn't for you then. Not every book has to be.

Maybe I was criticising the quoted text, not the book.

Re: Rust Atomics and Locks: Low-Level Concurrency in Practice

#47
The book, especially chapter 7 seems to say several things completely opposite to what Herb Sutter did in his famous talk a number of years ago.

In the book:

> This means that on ARM64, sequentially consistent operations are exactly as cheap as acquire and release operations. Or, rather, that ARM64 Acquire, Release, and AcqRel operations are as expensive as SeqCst. Unlike x86-64, however, Relaxed operations are relatively cheap, as they don’t result in stronger ordering guarantees than necessary.

However in Herb Sutter's talk (specifically the second half here: https://www.youtube.com/watch?v=KeLBd2EJLOU ) he says that ARMv8 Acquire/Release/AcqRel operations are cheaper than SeqCst. This book seems to claim that x64 is somehow superior to ARMv8, which from my understanding is completely wrong.

Re: Rust Atomics and Locks: Low-Level Concurrency in Practice

#48

The book, especially chapter 7 seems to say several things completely opposite to what Herb Sutter did in his famous talk a number of years ago. In the book: > This means that on ARM64, sequentially consistent operations are exactly as cheap as acquire and release operations. Or, rather, that ARM64 Acquire, Release, and AcqRel operations are as expensive as SeqCst. Unlike x86-64, however, Relaxed operations are relat…

Let's go to primary sources: https://developer.arm.com/documentation/ddi0487/latest

The author is right. ARMv8 supports relaxed memory ordering but its memory model does not support acquire-release ordering without sequential consistency. (Update: Support for weaker acquire-release ordering was added in a later revision.)

I'll quote a relevant excerpt from B2.3.11:

    Where a Load-Acquire appears in program order after a Store-Release, the memory access generated by the Store-Release instruction is Observed-by each PE to the extent that PE is required to observe the access coherently, before the memory access generated by the Load-Acquire instruction is Observed-by that PE, to the extent that the PE is required to observe the access coherently. 
Sequential consistency is needed to avoid store/load reordering in cases like this:

    // Thread 1
    flag1.store(1, SeqCst)
    if flag2.load(SeqCst) == 0 {
        // Guarded action
    }

    // Thread 2
    flag2.store(1, SeqCst)
    if flag1.load(SeqCst) == 0 {
        // Guarded action
    }
If these were instead implemented with acquire/release ordering as defined by the C++ or Rust memory model, the resulting happens-before constraints would not prevent both threads from executing their guarded actions.

The excerpt from the Architecture Reference Manual says that if you use their load-acquire (ldar) and store-release (stlr) instructions, it is not possible for the store-release to be moved after the load-acquire, as observed by PEs (processing elements, their abstraction of hardware threads).

Let's look at how C++ compilers implement acquire-release vs sequential consistency on x86 and ARMv8:

https://godbolt.org/z/3fd5jse18

The machine code on ARMv8 is identical for thread_acq_rel and thread_seq_cst. Whereas on x86 the thread_seq_cst code has to use xchg (an alternative to store + mfence) to achieve sequential consistency.

Update: shachaf pointed out that ARMv8 more recently added support for weaker acquire-release semantics in the ARMv8.3 revision. It looks like the first processor to ship with ARMv8.3 support was the A12X from Apple in 2018, which is 5 years after Herb's talk. If we take the code from before and compile for ARMv8 with all architectural features enabled, you will see different machine code for thread_acq_rel which uses the newer ldaprb instruction:

https://godbolt.org/z/dnP9sebcz

This illustrates a difficulty with talking about "ARMv8" as a fixed thing. It's much more of a rapidly moving target than x86. That said, the ARMv8.3 addendum should have been mentioned, at least parenthetically; I emailed the author suggesting an info box.

Post reply on HN