Skip to content

Commit 75024cc

Browse files
committed
Use atomic_read32_relaxed (lighter) when spinning instead of a sequential consistent read.
1 parent df3b74e commit 75024cc

4 files changed

Lines changed: 61 additions & 7 deletions

File tree

‎include/boost/interprocess/detail/atomic.hpp‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ BOOST_INTERPROCESS_FORCEINLINE boost::uint32_t atomic_read32(volatile boost::uin
7979
//! Atomically read an boost::uint32_t from memory with acquire semantics
8080
BOOST_INTERPROCESS_FORCEINLINE boost::uint32_t atomic_read32_acquire(volatile boost::uint32_t *mem);
8181

82+
//! Atomically read an boost::uint32_t from memory with relaxed semantics.
83+
BOOST_INTERPROCESS_FORCEINLINE boost::uint32_t atomic_read32_relaxed(volatile boost::uint32_t *mem);
84+
8285
//! Atomically set an boost::uint32_t in memory
8386
//! "mem": pointer to the object
8487
//! "param": val value that the object will assume
@@ -294,6 +297,26 @@ BOOST_INTERPROCESS_FORCEINLINE boost::uint32_t atomic_read32_acquire(volatile bo
294297
#endif
295298
}
296299

300+
//! Same as atomic_read32, but with relaxed semantics
301+
BOOST_INTERPROCESS_FORCEINLINE boost::uint32_t atomic_read32_relaxed(volatile boost::uint32_t *mem)
302+
{
303+
#if defined(__ATOMIC_RELAXED)
304+
//Clang (clang-cl included) and GCC: the same plain load instruction a
305+
//volatile read would give, but one that participates in the memory model,
306+
//so ThreadSanitizer does not flag it as racing other threads' writes
307+
return __atomic_load_n(mem, __ATOMIC_RELAXED);
308+
#elif defined(_M_ARM64EC) || defined(_M_ARM64) || defined(_M_ARM)
309+
//A plain 32 bit load the compiler cannot tear or widen and, unlike a
310+
//volatile read under /volatile:ms, one that carries no barrier
311+
return (boost::uint32_t)__iso_volatile_load32(reinterpret_cast<const volatile __int32*>(mem));
312+
#else
313+
//x86/x64: an aligned volatile load is a single MOV, and a relaxed load
314+
//needs no barrier, not even a compiler-only one: the value is a hint
315+
//that only a later acquire operation may act on
316+
return *mem;
317+
#endif
318+
}
319+
297320
//! Atomically set an boost::uint32_t in memory
298321
//! "mem": pointer to the object
299322
//! "param": val value that the object will assume
@@ -599,6 +622,22 @@ BOOST_INTERPROCESS_FORCEINLINE boost::uint32_t atomic_read32_acquire(volatile bo
599622
#endif
600623
}
601624

625+
//! Same as atomic_read32, but with relaxed semantics
626+
BOOST_INTERPROCESS_FORCEINLINE boost::uint32_t atomic_read32_relaxed(volatile boost::uint32_t *mem)
627+
{
628+
#if defined(__ATOMIC_RELAXED)
629+
//GCC 4.7 and later, Clang: the same plain load instruction a volatile
630+
//read would give, but one that participates in the memory model, so
631+
//ThreadSanitizer does not flag it as racing other threads' writes
632+
return __atomic_load_n(mem, __ATOMIC_RELAXED);
633+
#else
634+
//GCC 4.1 to 4.6: no __atomic builtins and no ThreadSanitizer either. An
635+
//aligned volatile load is a single load instruction on every supported
636+
//target, and a relaxed load needs no barrier around it
637+
return *mem;
638+
#endif
639+
}
640+
602641
//! Atomically set an boost::uint32_t in memory
603642
//! "mem": pointer to the object
604643
//! "param": val value that the object will assume

‎include/boost/interprocess/sync/detail/common_algorithms.hpp‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@ namespace ipcdetail {
3636
//////////////////////////////////////////////////////////////////////////////
3737
//
3838
// A mutex may offer "bool maybe_lockable()", a cheap, conservative answer to
39-
// "would try_lock() have a chance right now?". It is a hint, subject to two
39+
// "would try_lock() have a chance right now?". It is a hint, subject to three
4040
// rules:
4141
// - returning true is always allowed: the caller then performs the real
4242
// try_lock(), which is what actually decides,
4343
// - it must not keep returning false while the mutex is in fact free, or a
4444
// spinning caller would never take it. Re-reading the state each call is
45-
// enough to satisfy this.
45+
// enough to satisfy this,
46+
// - and, less obviously, a mutex whose try_lock() has side effects the
47+
// waiter depends on must NOT implement it. robust_spin_mutex is the
48+
// example
4649
//
4750
// The point is that for a spin mutex try_lock() is a read-modify-write, and a
4851
// failed one still takes the cache line for writing, so a loop that retries it

‎include/boost/interprocess/sync/spin/mutex.hpp‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ inline void spin_mutex::lock(void)
9696
ipcdetail::timeout_when_locking_aware_lock(clw);
9797
}
9898

99-
//A plain load, which only needs the cache line shared, where try_lock() needs
100-
//it exclusive. Used by the spin loops in common_algorithms.hpp to avoid
101-
//hammering the line while the mutex is held.
99+
//A relaxed load, which only needs the cache line shared, where try_lock()
100+
//needs it exclusive. Used by the spin loops to avoid
101+
//hammering the line while the mutex is held, as answer is only a hint.
102102
BOOST_INTERPROCESS_FORCEINLINE bool spin_mutex::maybe_lockable(void)
103-
{ return ipcdetail::atomic_read32(const_cast<boost::uint32_t*>(&m_s)) == 0u; }
103+
{ return ipcdetail::atomic_read32_relaxed(&m_s) == 0u; }
104104

105105
BOOST_INTERPROCESS_FORCEINLINE bool spin_mutex::try_lock(void)
106106
{

‎test/atomic_test.cpp‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using namespace boost::interprocess;
2121
using boost::interprocess::ipcdetail::atomic_read32;
2222
using boost::interprocess::ipcdetail::atomic_read32_acquire;
23+
using boost::interprocess::ipcdetail::atomic_read32_relaxed;
2324
using boost::interprocess::ipcdetail::atomic_write32;
2425
using boost::interprocess::ipcdetail::atomic_write32_release;
2526
using boost::interprocess::ipcdetail::atomic_add32;
@@ -58,19 +59,23 @@ void test_read_write()
5859

5960
BOOST_INTERPROCESS_CHECK(atomic_read32(&v) == 0u);
6061
BOOST_INTERPROCESS_CHECK(atomic_read32_acquire(&v) == 0u);
62+
BOOST_INTERPROCESS_CHECK(atomic_read32_relaxed(&v) == 0u);
6163

6264
atomic_write32(&v, 12345u);
6365
BOOST_INTERPROCESS_CHECK(atomic_read32(&v) == 12345u);
6466
BOOST_INTERPROCESS_CHECK(atomic_read32_acquire(&v) == 12345u);
67+
BOOST_INTERPROCESS_CHECK(atomic_read32_relaxed(&v) == 12345u);
6568

6669
atomic_write32_release(&v, 54321u);
6770
BOOST_INTERPROCESS_CHECK(atomic_read32(&v) == 54321u);
6871
BOOST_INTERPROCESS_CHECK(atomic_read32_acquire(&v) == 54321u);
72+
BOOST_INTERPROCESS_CHECK(atomic_read32_relaxed(&v) == 54321u);
6973

7074
//The whole 32 bit range must survive a write/read round trip
7175
const boost::uint32_t all_ones = ~boost::uint32_t(0);
7276
atomic_write32(&v, all_ones);
7377
BOOST_INTERPROCESS_CHECK(atomic_read32(&v) == all_ones);
78+
BOOST_INTERPROCESS_CHECK(atomic_read32_relaxed(&v) == all_ones);
7479
atomic_write32_release(&v, all_ones);
7580
BOOST_INTERPROCESS_CHECK(atomic_read32_acquire(&v) == all_ones);
7681

@@ -80,6 +85,7 @@ void test_read_write()
8085
for(int i = 0; i != 8; ++i){
8186
BOOST_INTERPROCESS_CHECK(atomic_read32(&v) == 7u);
8287
BOOST_INTERPROCESS_CHECK(atomic_read32_acquire(&v) == 7u);
88+
BOOST_INTERPROCESS_CHECK(atomic_read32_relaxed(&v) == 7u);
8389
}
8490
BOOST_INTERPROCESS_CHECK(v == 7u);
8591
}
@@ -315,7 +321,13 @@ class cas_lock_thread
315321
//the previous owner is visible to this thread
316322
spin_wait swait;
317323
while(atomic_cas32_acquire(&m_data->m_lock, 1u, 0u) != 0u){
318-
swait.yield();
324+
//Poll with a relaxed load until the lock looks free before
325+
//retrying the acquire CAS - spin_mutex's maybe_lockable()
326+
//idiom: a read only needs the cache line shared. The value is
327+
//just a hint, only the CAS above can actually take the lock
328+
while(atomic_read32_relaxed(&m_data->m_lock) != 0u){
329+
swait.yield();
330+
}
319331
}
320332
++m_data->m_guarded;
321333
//Unlock with release semantics, so that the write above is visible

0 commit comments

Comments
 (0)