Add spsc_q data structure library

This data structure is designed to support the use cases of the L1,
i.e., provide a queue which can be quickly used to order L1 jobs in FIFO
order with simple iteration of jobs to treat. It is a Single-Consumer
Single-Producer queue library.

Signed-off-by: Robert Schmidt <robert.schmidt@openairinterface.org>
This commit is contained in:
Robert Schmidt
2026-04-15 11:26:21 +02:00
parent 51e17fd7c8
commit 34aad5d276
6 changed files with 363 additions and 2 deletions
+8 -2
View File
@@ -8,5 +8,11 @@ add_test(NAME test_seq_arr COMMAND test_seq_arr) # no options required
add_executable(test_hashtable test_hashtable.cpp)
add_dependencies(tests test_hashtable)
target_link_libraries(test_hashtable PRIVATE ds GTest::gtest)
add_test(NAME test_hashtable
COMMAND ./test_hashtable)
add_test(NAME test_hashtable COMMAND ./test_hashtable)
add_executable(test_spsc_q test_spsc_q.cpp)
target_link_libraries(test_spsc_q ds GTest::gtest)
add_dependencies(tests test_spsc_q)
add_test(NAME test_spsc_q COMMAND test_spsc_q)
add_executable(test_spsc_q_perf test_spsc_q_perf.cpp)
target_link_libraries(test_spsc_q_perf PRIVATE ds benchmark::benchmark)
+135
View File
@@ -0,0 +1,135 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "gtest/gtest.h"
extern "C" {
#include "spsc_q.h"
}
TEST(spsc_q, basic_test) {
spsc_q_t rb = spsc_q_alloc(2, sizeof(int));
int a = 1;
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
EXPECT_FALSE(spsc_q_put(&rb, &a, sizeof(a)));
int b;
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
EXPECT_EQ(a, b);
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
EXPECT_EQ(a, b);
EXPECT_FALSE(spsc_q_get(&rb, &b, sizeof(b)));
spsc_q_free(&rb);
}
TEST(spsc_q, cont_write) {
spsc_q_t rb = spsc_q_alloc(10, sizeof(int));
int a = 1;
for (int i = 0; i < 1111; ++i) {
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
a++;
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
a++;
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
a++;
int b;
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
EXPECT_EQ(b, a - 3);
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
EXPECT_EQ(b, a - 2);
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
EXPECT_EQ(b, a - 1);
}
spsc_q_free(&rb);
}
bool count(const void *data, void *user)
{
EXPECT_TRUE(data != NULL);
int *c = (int *)user;
(*c)++;
return true; /* count all */
}
TEST(spsc_q, iterator) {
spsc_q_t rb = spsc_q_alloc(10, sizeof(int));
int n = 8;
for (int i = 0; i < n; ++i)
EXPECT_TRUE(spsc_q_put(&rb, &i, sizeof(i)));
int c = 0;
int results[n];
int ret = spsc_q_get_while(&rb, count, &c, results, sizeof(*results), n);
EXPECT_EQ(c, n);
EXPECT_EQ(ret, n);
for (int i = 0; i < n; ++i)
EXPECT_EQ(results[i], i);
int b;
EXPECT_FALSE(spsc_q_get(&rb, &b, sizeof(b))); /* empty! */
spsc_q_free(&rb);
}
bool drop(const void *data, void *user)
{
const int *e = (const int *)data;
int *c = (int *)user;
EXPECT_EQ(*e, *c);
(*c)++;
return true; /* drop all */
}
TEST(spsc_q, drop) {
spsc_q_t rb = spsc_q_alloc(10, sizeof(int));
int n = 8;
for (int i = 0; i < n; ++i)
EXPECT_TRUE(spsc_q_put(&rb, &i, sizeof(i)));
int c = 0;
int ret = spsc_q_drop_while(&rb, drop, &c);
EXPECT_EQ(c, n);
EXPECT_EQ(ret, n);
int b;
EXPECT_FALSE(spsc_q_get(&rb, &b, sizeof(b))); /* empty! */
spsc_q_free(&rb);
}
TEST(spsc_q, full) {
int n = 3;
spsc_q_t rb = spsc_q_alloc(n, sizeof(int));
int a = 1;
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
a++;
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
a++;
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
a++;
EXPECT_FALSE(spsc_q_put(&rb, &a, sizeof(a))); /* full! */
int b;
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
EXPECT_EQ(b, a - n);
EXPECT_TRUE(spsc_q_put(&rb, &a, sizeof(a)));
a++;
EXPECT_FALSE(spsc_q_put(&rb, &a, sizeof(a))); /* full! */
EXPECT_TRUE(spsc_q_get(&rb, &b, sizeof(b)));
EXPECT_EQ(b, a - n);
spsc_q_free(&rb);
}
int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
@@ -0,0 +1,75 @@
#include "benchmark/benchmark.h"
#include "stdio.h"
#include "assert.h"
#include "pthread.h"
extern "C" {
#include "spsc_q.h"
}
typedef struct q_args {
spsc_q_t q;
int range;
} q_args_t;
static void *producer(void *arg)
{
q_args_t *a = (q_args_t *) arg;
int i = 0;
while (i <= a->range) {
if (spsc_q_put(&a->q, &i, sizeof(i)))
i++;
}
return NULL;
}
static void *consumer(void *arg)
{
q_args_t *a = (q_args_t *) arg;
int i = 0;
while (i != a->range) {
int r;
if (spsc_q_get(&a->q, &r, sizeof(r))) {
assert(r == i);
i++;
}
}
return NULL;
}
static void run_test(int range, int q_size)
{
q_args_t args = {
.q = spsc_q_alloc(q_size, sizeof(int)),
.range = range,
};
pthread_t p, c;
int ret;
// start
ret = pthread_create(&p, NULL, producer, &args);
assert(ret == 0);
ret = pthread_create(&c, NULL, consumer, &args);
assert(ret == 0);
ret = pthread_join(p, NULL);
assert(ret == 0);
ret = pthread_join(c, NULL);
assert(ret == 0);
// stop
// data/time => throughput
spsc_q_free(&args.q);
}
static void BM_spsc_q(benchmark::State &state)
{
int range = 500000;
int q_size = state.range(0);
for (auto _ : state)
run_test(range, q_size);
}
BENCHMARK(BM_spsc_q)->RangeMultiplier(2)->Range(10, 160);
BENCHMARK_MAIN();