Live data from Hacker News

Programming Language Memory Models

research.swtch.com

71–80 of 101 posts

Re: Programming Language Memory Models

#71

"Java and JavaScript have avoided introducing weak (acquire/release) synchronizing atomics, which seem tailored for x86." This is not true for Java; see http://gee.cs.oswego.edu/dl/html/j9mm.html https://docs.oracle.com/en/java/javase/16/docs/api/java.base...

Its not true in general. x86 CANNOT have weak acquire/release semantics. x86 is "too strong", you get total-store ordering by default. If you want to test out weaker acquire/release semantics, you need to buy an ARM or POWER9 processor.

I would say that acquire/release map very well to x86 (were they are free). Technically x86 is slightly stronger as it doesn't allow IRIW, but seq cst is too expensive to implement by default.

Conversely acq/rel are from somewhat to very expensive to implement on ARM/POWER.

Re: Programming Language Memory Models

#72

Earlier quoted context omitted.

> I'll also note that using AcqRel semantics is not provided by the Rust version of compare_exchange_weak (perhaps a nit on TFA's assertion that Rust adopts the C++ memory model wholesale), so if acquire to lock the spinlock is not adequate, it's likely it would need to go to SeqCst. Is this true? AcqRel seems to be accepted by the compiler for the success ordering of compare_exchange_weak.

https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU32.h... It's accepted by the compiler, but if provided, it compiles to a panic.

On the page you linked panics are only mentioned for load and store and the code below seems to work just fine?

    let x = atomic::AtomicU32::new(0);
    x.compare_exchange_weak(
        0,
        1,
        atomic::Ordering::AcqRel,
        atomic::Ordering::Relaxed).unwrap();
    println!("{}", x.load(atomic::Ordering::Relaxed));

Re: Programming Language Memory Models

#73
post #35

Earlier quoted context omitted.

I have a feeling it's going to have C-like syntax and frankly I hope so because using an `end` keyword instead of braces makes no sense to me.

Arguably using curly braces to delineate blocks makes no inherent sense either. We just do it because that's what everybody else does.

So if I can give my extremely pedantic rebuff: `end` is 3 characters rather than two with `{}` - that's objectively more work to type, and it makes your programs take more space on disk.

Also it's dead simple to write parsers and developer tools which can match open and close braces. Handling `end` with an arbitrary opening token (maybe it's `if `, `while ` what have you) is objectively more work for your CPU to work with.

Subjectively, it looks dumb to have code which looks like this:

            end
          end
        end
      end
    end
  end

Re: Programming Language Memory Models

#74
post #64

Earlier quoted context omitted.

In Java, any object can be used to synchronize any data, e.g. synchronized(foobar_object){ foo(); } synchronized(foobar_object){ bar(); } synchronized(foobar_object){ baz(); } Will have foo, bar, baz methods well behaved in any data that they share regardless of whether they are foobar methods or methods of any other class(es). It is exactly analogous to the S(a) -> S(a) synchronizing instruction from the article tha…

Although in highly parallel code, the primitives from java.util.concurrent are to be preferred. I highly advise reading "Java Concurrency in Practice". Note that future Java primitive classes don't have monitors.

Seems like a vague way of saying that locks 'don't scale' or aren't composable, which is certainly the case but straying from the topic of memory models.

Re: Programming Language Memory Models

#75

A GPU followup to this article. While on CPU sequentially consistent semantics are efficient to implement, that seems to be much less true on GPU. Thus, Vulkan completely eliminates sequential consistency and provides only acquire/release semantics[1]. It is extremely difficult to reason about programs using these advanced memory semantics. For example, there is a discussion about whether a spinlock implemented in te…

There's even more discussion on the lock memory ordering on Stackoverflow: https://stackoverflow.com/questions/61299704/how-c-standard-...

Taking a lock only needs to be an acquire operation and a compiler barrier for other lock operations. Using seq_cst or acq_rel semantics is stronger than needed. From my reading and discussions with people from WG21 the current argument for why taking a lock only requires acq semantics is that a compiler optimization that transforms a non-deadlocking program into a potentially deadlocking program is not allowed. There's an interesting twitter thread where we discuss this I can't find anymore :(.

Re: Programming Language Memory Models

#76

A GPU followup to this article. While on CPU sequentially consistent semantics are efficient to implement, that seems to be much less true on GPU. Thus, Vulkan completely eliminates sequential consistency and provides only acquire/release semantics[1]. It is extremely difficult to reason about programs using these advanced memory semantics. For example, there is a discussion about whether a spinlock implemented in te…

Thanks for the GPU insights and links (and the paper link below)!

I based my claim about Rust from https://doc.rust-lang.org/nomicon/atomics.html. ("Rust pretty blatantly just inherits the memory model for atomics from C++20.") Perhaps that is out of date?

Re: Programming Language Memory Models

#77
post #75

A GPU followup to this article. While on CPU sequentially consistent semantics are efficient to implement, that seems to be much less true on GPU. Thus, Vulkan completely eliminates sequential consistency and provides only acquire/release semantics[1]. It is extremely difficult to reason about programs using these advanced memory semantics. For example, there is a discussion about whether a spinlock implemented in te…

There's even more discussion on the lock memory ordering on Stackoverflow: https://stackoverflow.com/questions/61299704/how-c-standard-... Taking a lock only needs to be an acquire operation and a compiler barrier for other lock operations. Using seq_cst or acq_rel semantics is stronger than needed. From my reading and discussions with people from WG21 the current argument for why taking a lock only requires acq sema…

That is an amazing thread. The fact that C++ apparently allows optimizing

    #include 
    
    int stop = 1;
    
    void maybeStop() {
        if(stop)
            for(;;);
    }
    
    int main() {
        printf("hello, ");
        maybeStop();
        printf("world\n");
    }
into

    int main() {
        printf("hello, world\n");
    }
(as Clang does today) does not inspire confidence about disallowing moving the loop in the other example. If the compiler is allowed to assume that this loop terminates, why not the lock loop?

Maybe there is a reason, but none of this inspires confidence.

Re: Programming Language Memory Models

#78

"Java and JavaScript have avoided introducing weak (acquire/release) synchronizing atomics, which seem tailored for x86." This is not true for Java; see http://gee.cs.oswego.edu/dl/html/j9mm.html https://docs.oracle.com/en/java/javase/16/docs/api/java.base...

Its not true in general. x86 CANNOT have weak acquire/release semantics. x86 is "too strong", you get total-store ordering by default. If you want to test out weaker acquire/release semantics, you need to buy an ARM or POWER9 processor.

ARMv7 or earlier it appears. On ARMv8 with direct hw support for SC atomics, the SC atomics are the suggested implementation of acq/rel too. See the ARMv8 section of https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html.

As I mentioned in the post (https://research.swtch.com/plmm#sc), Herb Sutter claimed in 2017 that POWER was going to do something to make SC atomics cheaper. If it did, then that might end up being cheaper than the old sync-based acq/rel too, same as ARM, in which case we'd end up with SC = acq/rel on both ARM and POWER. It looks like that didn't happen, but I'd be very interested to know what did, if anything.

Re: Programming Language Memory Models

#79
post #77
post #75

Earlier quoted context omitted.

There's even more discussion on the lock memory ordering on Stackoverflow: https://stackoverflow.com/questions/61299704/how-c-standard-... Taking a lock only needs to be an acquire operation and a compiler barrier for other lock operations. Using seq_cst or acq_rel semantics is stronger than needed. From my reading and discussions with people from WG21 the current argument for why taking a lock only requires acq sema…

That is an amazing thread. The fact that C++ apparently allows optimizing #include int stop = 1; void maybeStop() { if(stop) for(;;); } int main() { printf("hello, "); maybeStop(); printf("world\n"); } into int main() { printf("hello, world\n"); } (as Clang does today) does not inspire confidence about disallowing moving the loop in the other example. If the compiler is allowed to assume that this loop terminates, wh…

I don't remember the exact wording, but the standard explicitly makes an exception for the always terminating assumptions, for loops accessing atomic variables or having side effects (i.e. volatile or I/O).

Re: Programming Language Memory Models

#80
post #77
post #75

Earlier quoted context omitted.

There's even more discussion on the lock memory ordering on Stackoverflow: https://stackoverflow.com/questions/61299704/how-c-standard-... Taking a lock only needs to be an acquire operation and a compiler barrier for other lock operations. Using seq_cst or acq_rel semantics is stronger than needed. From my reading and discussions with people from WG21 the current argument for why taking a lock only requires acq sema…

That is an amazing thread. The fact that C++ apparently allows optimizing #include int stop = 1; void maybeStop() { if(stop) for(;;); } int main() { printf("hello, "); maybeStop(); printf("world\n"); } into int main() { printf("hello, world\n"); } (as Clang does today) does not inspire confidence about disallowing moving the loop in the other example. If the compiler is allowed to assume that this loop terminates, wh…

The standard says that a thread must eventually terminate, do an atomic operation or do IO. So the while(lock.exchange(true)); loop is different.

Also keep in mind that C++11 specifies std::mutex::lock() to have acquire semantics and unlock() to have release semantics on the lock object. In order for std::mutex to actually work the reordering of m1.unlock(); m2.lock(); to m2.lock(); m1.unlock(); must be disallowed. But since m1 and m2 are separate objects m1.unlock() has no happens before relationship with m2.lock(). This seems to be a problem in the C++11 memory model. The arguments I have heard from some WG21 people is that there is no problem since transforming a wellformed terminating program into a non-terminating program is not allowed. I can't find the wording in the C++ standard that asserts this. But oh well, it works right now on gcc/llvm/msvc.

Post reply on HN