Because it is SPSC, atomic variables are enough to synchronize the two
threads. Concretely put() is modified to ensure that read_idx is
"acquired" so that the read index has been written, including memory, by
the other thread. "Release" ensures that the write operation (including
the memory to the buffer) is written before it is visible to the other
thread in get() (which in turn "acquires" it). For more information, see
also [1].
The rest of the library has been simplified to work only with put() and
get(), reducing total code and the surface for possible bugs.
This (and the previous) version has been tested with the
threadSanitizer:
TSAN_OPTIONS=halt_on_error=1 ./common/utils/ds/tests/test_spsc_q_perf
On my machine, using Google Benchmark, I measure a considerable 5x speed
improvement:
$ /tmp/benchmark/tools/compare.py benchmarks pthread.json atomic.json
Comparing pthread.json to atomic.json
Benchmark Time CPU Time Old Time New CPU Old CPU New
--------------------------------------------------------------------------------------------------------------
BM_spsc_q/10 -0.8201 -0.0740 266779912 47989020 52387 48512
BM_spsc_q/16 -0.8301 -0.0520 249656592 42428540 51462 48784
BM_spsc_q/32 -0.8003 -0.0841 230248798 45972155 53841 49311
BM_spsc_q/64 -0.7995 -0.0506 210429791 42199674 50690 48124
BM_spsc_q/128 -0.7930 -0.1101 205212273 42483155 52745 46936
BM_spsc_q/160 -0.7880 -0.1663 216644738 45938247 53400 44518
OVERALL_GEOMEAN -0.8057 -0.0904 0 0 0 0
[1] https://en.cppreference.com/w/c/atomic/memory_order.html
Assisted-By: Claude:claude-sonnet-4-6
Signed-off-by: Robert Schmidt <robert.schmidt@openairinterface.org>
38 lines
918 B
C++
38 lines
918 B
C++
/*
|
|
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
|
*/
|
|
|
|
#ifndef SPSC_Q_H_
|
|
#define SPSC_Q_H_
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdatomic.h>
|
|
|
|
#if defined(__cplusplus)
|
|
// use atomic_size_t to help interworking with C++
|
|
using std::atomic_size_t;
|
|
#endif
|
|
|
|
typedef struct spsc_q {
|
|
uint8_t *buf;
|
|
size_t elsiz;
|
|
size_t cnt;
|
|
|
|
atomic_size_t write_idx;
|
|
atomic_size_t read_idx;
|
|
} spsc_q_t;
|
|
|
|
spsc_q_t spsc_q_alloc(size_t cnt, size_t elsiz);
|
|
void spsc_q_free(spsc_q_t *rb);
|
|
|
|
bool spsc_q_put(spsc_q_t *rb, const void *src, size_t elsiz);
|
|
bool spsc_q_get(spsc_q_t *rb, void *dest, size_t elsiz);
|
|
|
|
typedef bool (*pred)(const void *data, void *user);
|
|
bool spsc_q_get_if(spsc_q_t *rb, pred p, void *user, void *dest, size_t elsiz);
|
|
int spsc_q_drop_while(spsc_q_t *rb, pred p, void *user);
|
|
int spsc_q_get_while(spsc_q_t *rb, pred p, void *user, void *dest, size_t elsiz, size_t max_len);
|
|
|
|
#endif /* SPSC_Q_H_ */
|