mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
retro_atomic: add load_relaxed_size; use it for the owner's counter in spsc *_end
retro_spsc_write_end() published the new head by acquire-loading head
and adding to it; retro_spsc_read_end() did the same for tail. Both
counters are single-writer: head belongs to the producer and tail to
the consumer, so each *_end() is re-reading a value its own thread was
the last to store. No ordering is needed for that read.
This matters more than it looks: on the MSVC backend the acquire load
is InterlockedCompareExchange (a lock-prefixed RMW), and on the Apple
OSAtomic and __sync backends it is a barrier'd fetch_add(0). So every
span commit was paying for a locked instruction on its own cache line.
Add retro_atomic_load_relaxed_size() to every backend (C11/C++11/GCC
__atomic: memory_order_relaxed; MSVC/Apple/__sync/volatile: a plain
read of the volatile native-width typedef, which is guaranteed
tear-free on all of those targets) and use it in both *_end() paths.
The release store that actually publishes the counter is unchanged,
so the acquire/release pairing with the other side is intact.
Compile-checked with RETRO_ATOMIC_FORCE_{C11,CXX11,GCC_NEW,SYNC,
VOLATILE} and, via mingw-w64, FORCE_MSVC. samples/queues/
retro_spsc_test passes under TSan and ASan+UBSan.
This commit is contained in:
1 parent
914f0e26f1
commit
949c196315
2 files changed
+36
-4
No files matched your search
@@ -49,6 +49,11 @@
|
||||
* tags for every backend:
|
||||
*
|
||||
* retro_atomic_load_acquire - acquire load (pairs with release store)
|
||||
* retro_atomic_load_relaxed_size - relaxed load of a size_t; no ordering.
|
||||
* For a thread reading a counter that
|
||||
* only it writes (e.g. an SPSC producer
|
||||
* reading its own head). Not offered for
|
||||
* int: no caller needs it yet.
|
||||
* retro_atomic_store_release - release store (pairs with acquire load)
|
||||
* retro_atomic_fetch_add - acq_rel RMW
|
||||
* retro_atomic_fetch_sub - acq_rel RMW
|
||||
@@ -391,6 +396,8 @@ typedef atomic_size_t retro_atomic_size_t;
|
||||
|
||||
#define retro_atomic_load_acquire_size(p) \
|
||||
atomic_load_explicit((p), memory_order_acquire)
|
||||
#define retro_atomic_load_relaxed_size(p) \
|
||||
atomic_load_explicit((p), memory_order_relaxed)
|
||||
#define retro_atomic_store_release_size(p, v) \
|
||||
atomic_store_explicit((p), (v), memory_order_release)
|
||||
#define retro_atomic_fetch_add_size(p, v) \
|
||||
@@ -448,6 +455,8 @@ typedef std::atomic<std::size_t> retro_atomic_size_t;
|
||||
|
||||
#define retro_atomic_load_acquire_size(p) \
|
||||
std::atomic_load_explicit((p), std::memory_order_acquire)
|
||||
#define retro_atomic_load_relaxed_size(p) \
|
||||
std::atomic_load_explicit((p), std::memory_order_relaxed)
|
||||
#define retro_atomic_store_release_size(p, v) \
|
||||
std::atomic_store_explicit((p), (std::size_t)(v), std::memory_order_release)
|
||||
#define retro_atomic_fetch_add_size(p, v) \
|
||||
@@ -482,6 +491,8 @@ typedef size_t retro_atomic_size_t;
|
||||
|
||||
#define retro_atomic_load_acquire_size(p) \
|
||||
__atomic_load_n((p), __ATOMIC_ACQUIRE)
|
||||
#define retro_atomic_load_relaxed_size(p) \
|
||||
__atomic_load_n((p), __ATOMIC_RELAXED)
|
||||
#define retro_atomic_store_release_size(p, v) \
|
||||
__atomic_store_n((p), (v), __ATOMIC_RELEASE)
|
||||
#define retro_atomic_fetch_add_size(p, v) \
|
||||
@@ -620,6 +631,12 @@ static INLINE LONG retro_atomic_fetch_and_int_cas(LONG volatile *p, LONG v)
|
||||
* needs seq_cst, they can pair this with an additional load_acquire
|
||||
* on the same variable. */
|
||||
|
||||
/* Relaxed load: retro_atomic_size_t is volatile LONG_PTR, so a plain
|
||||
* read is an aligned native-width volatile load, which MSVC guarantees
|
||||
* is atomic (no tearing) on every architecture it targets. This is
|
||||
* the one load here that is not a locked RMW. */
|
||||
#define retro_atomic_load_relaxed_size(p) ((size_t)(*(p)))
|
||||
|
||||
#if defined(_WIN64)
|
||||
#define retro_atomic_load_acquire_size(p) \
|
||||
((size_t)InterlockedCompareExchangeAcquire64((LONGLONG volatile*)(p), 0, 0))
|
||||
@@ -680,6 +697,11 @@ typedef volatile intptr_t retro_atomic_size_t;
|
||||
#define retro_atomic_fetch_and_int(p, v) \
|
||||
((int)OSAtomicAnd32OrigBarrier((uint32_t)(v), (volatile uint32_t*)(p)))
|
||||
|
||||
/* Relaxed load: aligned native-width volatile read, atomic on every
|
||||
* Apple target (no tearing); unlike the barrier'd Add(0) above it is
|
||||
* not an RMW. */
|
||||
#define retro_atomic_load_relaxed_size(p) ((size_t)(*(p)))
|
||||
|
||||
#if defined(__LP64__)
|
||||
#define retro_atomic_load_acquire_size(p) \
|
||||
((size_t)OSAtomicAdd64Barrier(0, (volatile int64_t*)(p)))
|
||||
@@ -730,6 +752,8 @@ typedef volatile size_t retro_atomic_size_t;
|
||||
|
||||
#define retro_atomic_load_acquire_size(p) \
|
||||
__sync_fetch_and_add((p), (size_t)0)
|
||||
/* Relaxed load: aligned volatile size_t read, no RMW. */
|
||||
#define retro_atomic_load_relaxed_size(p) (*(p))
|
||||
#define retro_atomic_store_release_size(p, v) \
|
||||
do { __sync_synchronize(); *(p) = (v); __sync_synchronize(); } while (0)
|
||||
#define retro_atomic_fetch_add_size(p, v) \
|
||||
@@ -775,6 +799,7 @@ static INLINE int retro_atomic_fetch_and_int_fb(retro_atomic_int_t *p, int v)
|
||||
}
|
||||
|
||||
#define retro_atomic_load_acquire_size(p) (*(p))
|
||||
#define retro_atomic_load_relaxed_size(p) (*(p))
|
||||
#define retro_atomic_store_release_size(p, v) do { *(p) = (v); } while (0)
|
||||
#define retro_atomic_fetch_add_size(p, v) ((*(p) += (v)) - (v))
|
||||
#define retro_atomic_fetch_sub_size(p, v) ((*(p) -= (v)) + (v))
|
||||
|
||||
@@ -227,10 +227,15 @@ void retro_spsc_write_end(retro_spsc_t *q, size_t bytes)
|
||||
{
|
||||
if (bytes == 0)
|
||||
return;
|
||||
/* Release: the caller's stores into the span happen-before the
|
||||
/* head is written only by the producer, i.e. by this thread, so
|
||||
* reading our own last store needs no ordering: a relaxed load is
|
||||
* enough (and on the MSVC/Apple/__sync backends the acquire load is
|
||||
* a locked RMW, so this also removes a lock-prefixed instruction
|
||||
* from every span commit).
|
||||
* Release: the caller's stores into the span happen-before the
|
||||
* consumer's acquire-load of head, same pairing as retro_spsc_write. */
|
||||
retro_atomic_store_release_size(&q->head,
|
||||
retro_atomic_load_acquire_size(&q->head) + bytes);
|
||||
retro_atomic_load_relaxed_size(&q->head) + bytes);
|
||||
}
|
||||
|
||||
size_t retro_spsc_read_begin(retro_spsc_t *q, const void **ptr)
|
||||
@@ -253,10 +258,12 @@ void retro_spsc_read_end(retro_spsc_t *q, size_t bytes)
|
||||
{
|
||||
if (bytes == 0)
|
||||
return;
|
||||
/* Release: our reads of the span happen-before the producer's
|
||||
/* tail is written only by the consumer, i.e. by this thread, so a
|
||||
* relaxed load of our own counter suffices; see retro_spsc_write_end.
|
||||
* Release: our reads of the span happen-before the producer's
|
||||
* acquire-load of tail sees the space as free. */
|
||||
retro_atomic_store_release_size(&q->tail,
|
||||
retro_atomic_load_acquire_size(&q->tail) + bytes);
|
||||
retro_atomic_load_relaxed_size(&q->tail) + bytes);
|
||||
}
|
||||
|
||||
size_t retro_spsc_skip(retro_spsc_t *q, size_t bytes)
|
||||
|
||||
Reference in new issue
Block a user