One of my programs contains some old code for implementing a memory barrier using OS/compiler specific APIs; it looks like this:
inline void memory_barrier()
{
#if defined(ATOMIC_APPLE)
OSMemoryBarrier();
#elif defined(ATOMIC_NEWER_MSC_VER)
_ReadWriteBarrier();
#elif defined(ATOMIC_NEWER_GNUC)
__sync_synchronize();
#else
# error "no memory barrier implemented for this platform"
#endif
}
... however, the APIs called by this function are non-portable, and also they cause "deprecated function" warnings to appear in my compile output, so I'd like to replace them with something from the C++11 standard library instead. After some googling, I came up with this as a potential replacement:
inline void memory_barrier()
{
std::atomic_thread_fence(std::memory_order_seq_cst);
}
My question is, is this new function a logically equivalent replacement for the old one? Or do I need something different in order to keep the same semantics as before?
Looking at the documentation of the non-portable methods your old code used, short answer: the C++11 code will safely replace all of the previous functions.
However, std::atomic_thread_fence
will both be a compiler barrier (disallowing the compiler to reorder instructions across the barrier) as well as a CPU barrier for that given thread (disallowing the CPU to reorder instructions across the barrier). The previous methods you've used don't actually have exactly the same semantics.
_ReadWriteBarrier
(Windows) is just a barrier for the compiler and not a barrier for the CPUOSMemoryBarrier
(macOS) is actually a direct replacement for std::atomic_thread_fence(std::memory_order_seq_cst)
. And it's deprecated btw.__sync_synchronize
(GCC) is also a direct replacement for std::atomic_thread_fence(std::memory_order_seq_cst)
.If you just want a barrier for the compiler (e.g. the current Windows version of the code), you can use std::atomic_signal_fence instead of std::atomic_thread_fence.
Since the old code wasn't completely clear on what its intention is, I'd personally err more on the side of caution and use the std::atomic_thread_fence
you've suggested (and not the compiler-only barrier), because I suspect that the original code on Windows was actually conceptually wrong (and it just happened to work by accident). This could technically impact performance slightly, depending on how this was used previously, but only go to a compiler-only fence if you are really sure that that is semantically correct.
A small other remark, because I saw it in the old code you were replacing: you had a check #elif defined(ATOMIC_NEWER_MSC_VER)
in there to check for MSVC on Windows. I've seen this a lot, and I think this makes software far less portable to other compilers, since most other compilers on Windows (such as MinGW) also provide most of the intrinsic functions in their own headers, so I don't recommend detecting Windows via #ifdef
s for the MSVC compiler, but rather via platform macros (e.g. #ifdef _WIN32
). Only check for the MSVC compiler for actual compiler features that aren't provided by other compilers, such as #pragma comment(lib, ...)
. (In this case this doesn't matter since you are replacing the old code with portable C++11 code, which is great, but in case you run across that in the rest of your codebase.)