Who ordered memory fences on an x86? (2008)
bartoszmilewski.com
bartoszmilewski.com
> Since fences are so expensive, couldn’t you add a dummy assembly instruction that prevents the X86 from re-ordering? So the pseudo assembly for thread 0 might become:
> store(zeroWants, 1)
> store(victim, 0)
> r0 = load(victim) ; NEW DUMMY INSTRUCTION
> r0 = load(oneWants)
> r1 = load(victim)
> Since the dummy instruction is a load, the X86 can’t reorder the other loads before it. Also, since it loads from “victim”, the X86 can’t move it before the store to “victim”.> If you do this to both threads, does that solve the problem?
This doesn't work. Intel specifically calls out these sorts of attempts to get a fake memory barrier: "The memory-ordering model allows concurrent stores by two processors to be seen in different orders by those two processors; specifically, each processor may perceive its own store occurring before that of the other." This is true in practice as well as in theory.
A related trick that does work in practice (though it is also banned by Intel) is to write to the low-order bytes of a word, read the entire word, and get the high-order bytes. It's sort of a single-word StoreLoad barrier. There's a paper from Sun that documents it further: http://home.comcast.net/~pjbishop/Dave/QRL-OpLocks-BiasedLoc... .
In regular code you should never require the hammer of mfence for correct synchronization. You can implement C++11 atomics without it.
[edit] "never require the hammer of mfence for correct synchronization", maybe you're confining this to correct synch. and not recovering sequential consistency (or some other semantic property).
- You don't need mfence (or sfence / lfence) on x64 (which is actually what I care about rather than x86, though they're essentially the same in this respect) to correctly implement C++11 atomics with acquire / release semantics. You can get all the guarantees you need with locked instructions.
- Correctly implemented C++11 atomics implemented with locked instructions will be as fast or faster than when implemented with explicit fence instructions.
- The obvious way to implement standalone fences on x64 is with fence instructions but you rarely if ever need standalone fences. Generally you are better off using atomic operations with explicit acquire / release semantics.
In the codebase I was maintaining, the pre-C++11 atomic library was based around explicit fences rather than C++11 style atomic operations with acquire / release semantics attached to the operations themselves. This code was primarily written for Gen 3 consoles (PS3 / Xbox 360) and so was optimized for PowerPC. On x64 (Gen 4 consoles!) there was measurable performance overhead due to unnecessary/redundant standalone fences.
We decided it was too risky to try and rewrite everything in terms of atomic operations with acquire release semantics and remove the standalone fences in the end but it seems to me that if you want to write efficient cross architecture lock free code you want to avoid standalone fences and use a C++11 style atomics library where acquire release semantics are tied to the atomic operations themselves.
http://stackoverflow.com/questions/4972106/sequentially-cons...
You may be thinking of sfence here, not mfence.
> In regular code you should never require the hammer of mfence for correct synchronization. You can implement C++11 atomics without it.
That's true, but I think that, in many circumstances, mfence is cheaper than atomic RMW instructions.
EDIT: Maybe I'm wrong. lock addl to the stack is faster than mfence on my laptop, but not that much faster. This is strange, because in most cases you can replace mfence with a locked access to an unrelated memory location. sfence is much faster than either.
As an aside, g++ uses mfence for its C++11 atomics while clang uses xchgl; Does anyone know if Intel/AMD has ever guaranteed that all the usual ordering guarantees apply when mixing these? (Of course given their implementations it almost certainly will, but it would be nice to know this for a fact).
This is true for regular race-free multithreaded programs. However, mfences are used fairly commonly in lock-free, concurrent data structures (http://concurrencykit.org/)
Wonder why that guarantee is necessary. Loads have no side-effects (in the memory), after all.
This requirement is needed for:
T1: rd y; rd x;
T2: wr y 1;Wr x 1;
If you allow reordering its possible for an execution to observe x=0 y=1 on thread one, but that's a sequential consistency violation.