Skip to content

Commit 365ff57

Browse files
committed
ringbuf2: Replacement for ringbuf.
I found the ringbuf somewhat clunky and hard to use. Replace it by a lighter weight, non-macro based ring buffer with a more useful API.
1 parent 36bfd35 commit 365ff57

File tree

5 files changed

+309
-0
lines changed

5 files changed

+309
-0
lines changed

src/csnip/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ set(public_headers
121121
podtypes.h
122122
preproc.h
123123
ringbuf.h
124+
ringbuf2.h
124125
rng.h
125126
rng_mt.h
126127
runif.h
@@ -137,6 +138,7 @@ set(c_sources
137138
log.c
138139
meanvar.c
139140
mem.c
141+
ringbuf2.c
140142
rng.c
141143
rng_mt.c
142144
runif.c

src/csnip/ringbuf2.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#include <assert.h>
2+
3+
#define CSNIP_SHORT_NAMES
4+
#include <csnip/util.h>
5+
#include <csnip/ringbuf2.h>
6+
7+
size_t ringbuf2_init(ringbuf2* rb, size_t min_cap)
8+
{
9+
/* The actual capacity must be a power of 2 (for otherwise, a
10+
* wrap in the n_written or n_read indices would corrupt the
11+
* write/read position index.)
12+
*
13+
* We pick the next larger power of 2.
14+
*/
15+
const size_t cap = next_pow_of_2(min_cap);
16+
17+
*rb = (ringbuf2) {
18+
.cap = cap,
19+
.n_written = 0,
20+
.n_read = 0
21+
};
22+
23+
return cap;
24+
}
25+
26+
ringbuf2 ringbuf2_make(size_t min_cap)
27+
{
28+
ringbuf2 rb;
29+
ringbuf2_init(&rb, min_cap);
30+
return rb;
31+
}
32+
33+
size_t ringbuf2_used_size(const ringbuf2* rb)
34+
{
35+
return rb->n_written - rb->n_read;
36+
}
37+
38+
size_t ringbuf2_free_size(const ringbuf2* rb)
39+
{
40+
return rb->cap - ringbuf2_used_size(rb);
41+
}
42+
43+
size_t ringbuf2_get_write_idx(const ringbuf2* rb, size_t* ret_contig_write_max)
44+
{
45+
const size_t write_idx = rb->n_written & (rb->cap - 1);
46+
if (ret_contig_write_max) {
47+
const size_t n_free = ringbuf2_free_size(rb);
48+
const size_t n_to_end = rb->cap - write_idx;
49+
*ret_contig_write_max = Min(n_free, n_to_end);
50+
}
51+
return write_idx;
52+
}
53+
54+
bool ringbuf2_add_written(ringbuf2* rb, size_t n_written)
55+
{
56+
rb->n_written += n_written;
57+
return rb->n_written - rb->n_read <= rb->cap;
58+
}
59+
60+
size_t ringbuf2_get_read_idx(const ringbuf2* rb, size_t* ret_contig_read_max)
61+
{
62+
const size_t read_idx = rb->n_read & (rb->cap - 1);
63+
if (ret_contig_read_max) {
64+
const size_t n_used = ringbuf2_used_size(rb);
65+
const size_t n_to_end = rb->cap - read_idx;
66+
*ret_contig_read_max = Min(n_used, n_to_end);
67+
}
68+
return read_idx;
69+
}
70+
71+
bool ringbuf2_add_read(ringbuf2* rb, size_t n_read)
72+
{
73+
rb->n_read += n_read;
74+
return rb->n_read <= rb->n_written;
75+
}

src/csnip/ringbuf2.h

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#ifndef CSNIP_RINGBUF2_H
2+
#define CSNIP_RINGBUF2_H
3+
4+
/** @file ringbuf2.h
5+
* @brief Ring buffers, v2
6+
* @defgroup ringbuf2 Ring buffers, v2
7+
* @{
8+
*
9+
* @brief Ring buffer implementation, v2
10+
*
11+
* Implements a simple ring buffer with a capacity of a power of 2.
12+
* This module only manages the indices, the user needs to keep a
13+
* backing array separately.
14+
*
15+
* This module is intended as a replacement for csnip's first
16+
* ringbuf module; the former has been found to be clunky to use
17+
* in practice.
18+
*
19+
* No type generic macros are provided here, as the data structure
20+
* has a fixed type.
21+
*
22+
* The data structure used here, making use of controlled unsigned
23+
* integer overflow is inspired by io_uring's communication ring
24+
* buffers, see https://kernel.dk/io_uring.pdf
25+
*/
26+
27+
#include <stdbool.h>
28+
#include <stddef.h>
29+
30+
typedef struct {
31+
/** buffer capacity.
32+
*
33+
* Must be a power of 2.
34+
*/
35+
size_t cap;
36+
37+
/** Number of elements written so far.
38+
*
39+
* The number is mod (SIZE_MAX + 1), i.e., using C's
40+
* wrapping unsigned integer arithmetic.
41+
*/
42+
size_t n_written;
43+
44+
/** Number of elements read so far.
45+
*
46+
* The number is mod (SIZE_MAX + 1), using C's wrapping
47+
* unsigned integer arithmetic.
48+
*/
49+
size_t n_read;
50+
} csnip_ringbuf2;
51+
52+
size_t csnip_ringbuf2_init(csnip_ringbuf2* rb, size_t min_cap);
53+
csnip_ringbuf2 csnip_ringbuf2_make(size_t min_cap);
54+
55+
size_t csnip_ringbuf2_used_size(const csnip_ringbuf2* rb);
56+
size_t csnip_ringbuf2_free_size(const csnip_ringbuf2* rb);
57+
58+
size_t csnip_ringbuf2_get_write_idx(const csnip_ringbuf2* rb,
59+
size_t* ret_contig_write_max);
60+
bool csnip_ringbuf2_add_written(csnip_ringbuf2* rb, size_t n_written);
61+
62+
size_t csnip_ringbuf2_get_read_idx(const csnip_ringbuf2* rb,
63+
size_t* ret_contig_read_max);
64+
bool csnip_ringbuf2_add_read(csnip_ringbuf2* rb, size_t n_read);
65+
66+
#endif /* CSNIP_RINGBUF2_H */
67+
68+
#if defined(CSNIP_SHORT_NAMES) && !defined(CSNIP_RINGBUF2_HAVE_SHORT_NAMES)
69+
#define ringbuf2 csnip_ringbuf2
70+
#define ringbuf2_init csnip_ringbuf2_init
71+
#define ringbuf2_make csnip_ringbuf2_make
72+
#define ringbuf2_used_size csnip_ringbuf2_used_size
73+
#define ringbuf2_free_size csnip_ringbuf2_free_size
74+
#define ringbuf2_get_write_idx csnip_ringbuf2_get_write_idx
75+
#define ringbuf2_add_written csnip_ringbuf2_add_written
76+
#define ringbuf2_get_read_idx csnip_ringbuf2_get_read_idx
77+
#define ringbuf2_add_read csnip_ringbuf2_add_read
78+
#define CSNIP_RINGBUF2_HAVE_SHORT_NAMES
79+
#endif /* CSNIP_SHORT_NAMES && !CSNIP_RINGBUF2_HAVE_SHORT_NAMES */

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ set(tests_c
1616
meanvar_test0.c
1717
mem_test0.c
1818
ringbuf_test.c
19+
ringbuf2_test.c
1920
# rng_mt_test.c
2021
runif_getf_test.c
2122
runif_geti_test.c

test/ringbuf2_test.c

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#include <stdlib.h>
2+
#include <stdio.h>
3+
#include <string.h>
4+
5+
#define CSNIP_SHORT_NAMES
6+
#include <csnip/ringbuf2.h>
7+
#include <csnip/util.h>
8+
9+
#define CHECK(x) \
10+
do { \
11+
if (!(x)) { \
12+
printf(" FAIL\n"); \
13+
fprintf(stderr, "check \"%s\" failed\n", #x); \
14+
exit(1); \
15+
} \
16+
} while (0)
17+
18+
static void test_init_k(int k, int expect)
19+
{
20+
ringbuf2 rb;
21+
memset(&rb, 'x', sizeof rb);
22+
23+
CHECK(ringbuf2_init(&rb, k) == expect);
24+
CHECK(rb.cap == expect);
25+
CHECK(rb.n_written == 0);
26+
CHECK(rb.n_read == 0);
27+
28+
ringbuf2 rb2 = ringbuf2_make(k);
29+
CHECK(rb2.cap == expect);
30+
CHECK(rb2.n_written == 0);
31+
CHECK(rb2.n_read == 0);
32+
}
33+
34+
void test_init(void)
35+
{
36+
test_init_k(15, 16);
37+
test_init_k(16, 16);
38+
test_init_k(17, 32);
39+
}
40+
41+
/* Create example ring buffers
42+
*
43+
* - straight has the write index after read
44+
* - twisted has the write index before read
45+
*/
46+
47+
size_t rand_i(int i)
48+
{
49+
size_t ii = i;
50+
for (int j = 0; j < 8; ++j) {
51+
ii *= 1234567u;
52+
ii ^= ii >> 12;
53+
}
54+
return ii;
55+
}
56+
57+
ringbuf2 get_rb2_straight(int i, size_t* ret_widx, size_t* ret_ridx)
58+
{
59+
/* Capacity */
60+
const size_t cap = ((size_t)1) << (rand_i(i) & 31);
61+
62+
/* Indices */
63+
size_t a = rand_i(i + 1) % cap;
64+
size_t b = rand_i(i + 2) % cap;
65+
const size_t widx = Max(a, b);
66+
const size_t ridx = Min(a, b);
67+
68+
ringbuf2 rb = {
69+
.cap = cap,
70+
.n_written = widx + i * cap,
71+
.n_read = ridx + i * cap,
72+
};
73+
74+
*ret_widx = widx;
75+
*ret_ridx = ridx;
76+
return rb;
77+
}
78+
79+
ringbuf2 get_rb2_twisted(int i, size_t* ret_widx, size_t* ret_ridx)
80+
{
81+
size_t widx0, ridx0;
82+
ringbuf2 rb_s = get_rb2_straight(i, &widx0, &ridx0);
83+
ringbuf2 rb = (ringbuf2) {
84+
.cap = rb_s.cap,
85+
.n_written = ridx0 + rb_s.cap,
86+
.n_read = widx0,
87+
};
88+
89+
*ret_widx = ridx0;
90+
*ret_ridx = widx0;
91+
return rb;
92+
}
93+
94+
void straighttwisted_checks(void)
95+
{
96+
for (int i = 0; i < 1024; ++i) {
97+
/* Straight checks */
98+
printf("Straight checks, i = %d\n", i);
99+
size_t widx, ridx;
100+
ringbuf2 rb = get_rb2_straight(i, &widx, &ridx);
101+
CHECK(ringbuf2_used_size(&rb) == widx - ridx);
102+
CHECK(ringbuf2_free_size(&rb)
103+
== rb.cap - ringbuf2_used_size(&rb));
104+
CHECK(ringbuf2_get_write_idx(&rb, NULL) == widx);
105+
CHECK(ringbuf2_get_read_idx(&rb, NULL) == ridx);
106+
size_t n_contig;
107+
CHECK(ringbuf2_get_write_idx(&rb, &n_contig) == widx);
108+
CHECK(n_contig == rb.cap - widx);
109+
CHECK(ringbuf2_get_read_idx(&rb, &n_contig) == ridx);
110+
CHECK(n_contig == widx - ridx);
111+
if (ringbuf2_free_size(&rb) > 0) {
112+
CHECK(ringbuf2_add_written(&rb, 1));
113+
CHECK(ringbuf2_get_write_idx(&rb, NULL)
114+
== (widx + 1) % rb.cap);
115+
}
116+
if (ringbuf2_used_size(&rb) > 0) {
117+
CHECK(ringbuf2_add_read(&rb, 1));
118+
CHECK(ringbuf2_get_read_idx(&rb, NULL)
119+
== (ridx + 1) % rb.cap);
120+
}
121+
122+
/* Twisted checks */
123+
printf("Twisted checks, i = %d\n", i);
124+
rb = get_rb2_twisted(i, &widx, &ridx);
125+
CHECK(ringbuf2_used_size(&rb) == rb.cap + widx - ridx);
126+
CHECK(ringbuf2_free_size(&rb) == ridx - widx);
127+
CHECK(ringbuf2_get_write_idx(&rb, NULL) == widx);
128+
CHECK(ringbuf2_get_read_idx(&rb, NULL) == ridx);
129+
CHECK(ringbuf2_get_write_idx(&rb, &n_contig) == widx);
130+
CHECK(n_contig == ridx - widx);
131+
CHECK(ringbuf2_get_read_idx(&rb, &n_contig) == ridx);
132+
CHECK(n_contig == rb.cap - ridx);
133+
if (ringbuf2_free_size(&rb) > 0) {
134+
CHECK(ringbuf2_add_written(&rb, 1));
135+
CHECK(ringbuf2_get_write_idx(&rb, NULL)
136+
== (widx + 1) % rb.cap);
137+
}
138+
if (ringbuf2_used_size(&rb) > 0) {
139+
CHECK(ringbuf2_add_read(&rb, 1));
140+
CHECK(ringbuf2_get_read_idx(&rb, NULL)
141+
== (ridx + 1) % rb.cap);
142+
}
143+
}
144+
}
145+
146+
147+
int main(int argc, char** argv)
148+
{
149+
test_init();
150+
straighttwisted_checks();
151+
return 0;
152+
}

0 commit comments

Comments
 (0)