Created
March 1, 2018 14:02
-
-
Save riptl/b527326941927364da03c04a9c3582e8 to your computer and use it in GitHub Desktop.
C Unmasked Ring Buffer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdlib.h> | |
#include <string.h> | |
#include "ringbuf.h" | |
// Unmasked implementation | |
inline static size_t unmasked_inc(size_t index, size_t size); | |
inline static size_t unmasked_inc(size_t index, size_t size) | |
{ | |
return index != SIZE_MAX | |
? index + 1 | |
: (SIZE_MAX % size) + 1; // Rewind | |
} | |
mdfy_ring_t *mdfy_create_ring(size_t size) | |
{ | |
mdfy_ring_t tmp = { | |
calloc(size, sizeof(float)), | |
size, 0, 0 | |
}; | |
mdfy_ring_t *r = malloc(sizeof(mdfy_ring_t)); | |
memcpy(r, &tmp, sizeof(mdfy_ring_t)); | |
return r; | |
} | |
void mdfy_destroy_ring(mdfy_ring_t *r) | |
{ | |
free(r); | |
} | |
_Bool mdfy_ring_empty(mdfy_ring_t *r) | |
{ | |
return r->head == r->tail; | |
} | |
_Bool mdfy_ring_full(mdfy_ring_t *r) | |
{ | |
return mdfy_ring_used(r) == r->size; | |
} | |
size_t mdfy_ring_used(mdfy_ring_t *r) | |
{ | |
return r->head - r->tail; | |
} | |
size_t mdfy_ring_free(mdfy_ring_t *r) | |
{ | |
return r->size - mdfy_ring_used(r); | |
} | |
mdfy_ring_t *mdfy_ring_resize(mdfy_ring_t *r, size_t newSize) | |
{ | |
if (mdfy_ring_used(r) > newSize) | |
return NULL; | |
float *newBuf; | |
size_t realHead = (r->head % r->size); | |
size_t realTail = (r->tail % r->size); | |
size_t newHead, newTail; | |
if ((realHead > realTail || r->tail == r->head) && newSize > r->size) { | |
// Easy resize | |
newBuf = realloc(r->buf, newSize); | |
newHead = r->head; | |
newTail = r->tail; | |
} else { | |
// Cache current indices | |
size_t oldHead = r->head; | |
size_t oldTail = r->tail; | |
// Copy necessary | |
newBuf = calloc(newSize, sizeof(float)); | |
newHead = mdfy_ring_read(r, newBuf, newSize); | |
newTail = 0; | |
// Revert from read | |
r->head = oldHead; | |
r->tail = oldTail; | |
} | |
// Allocation failed | |
if (newBuf == NULL) | |
return NULL; | |
mdfy_ring_t tmp = { | |
newBuf, newSize, | |
newHead, newTail | |
}; | |
mdfy_ring_t *newRing = malloc(sizeof(mdfy_ring_t)); | |
memcpy(newRing, &tmp, sizeof(mdfy_ring_t)); | |
return newRing; | |
} | |
mdfy_ring_t *mdfy_ring_promise(mdfy_ring_t *r, size_t minSize) | |
{ | |
if (r->size >= minSize) { | |
return r; | |
} else { | |
size_t stdResize = (3 * r->size) / 8; | |
// newSize = max(stdResize, minSize) | |
size_t newSize = minSize > stdResize ? minSize : stdResize; | |
return mdfy_ring_resize(r, newSize); | |
} | |
} | |
void mdfy_ring_clear(mdfy_ring_t *r) | |
{ | |
memset(r->buf, 0, r->size * sizeof(float)); | |
r->head = 0; | |
r->tail = 0; | |
} | |
size_t mdfy_ring_read(mdfy_ring_t *r, float *data, size_t len) | |
{ | |
size_t avl = mdfy_ring_used(r); | |
if (len > avl) | |
len = avl; | |
if (len == 0) | |
return 0; | |
size_t realHead = (r->head % r->size); | |
size_t realTail = (r->tail % r->size); | |
size_t split = r->size - realTail; | |
if (realHead > realTail || len < split) { | |
// One copy needed | |
// Tail does not wrap around | |
memcpy(data, r->buf + realTail, len * sizeof(float)); | |
} else { | |
// Update both halves | |
// Tail wraps around | |
memcpy(data, r->buf + realTail, split * sizeof(float)); | |
size_t part2 = len - split; | |
memcpy(data + split, r->buf, part2 * sizeof(float)); | |
} | |
size_t newTail = r->tail + len; | |
if (newTail >= r->tail) { | |
r->tail = newTail; | |
} else { | |
// Overflow | |
r->tail = realTail + 1; | |
} | |
return len; | |
} | |
size_t mdfy_ring_write(mdfy_ring_t *r, float *data, size_t len) | |
{ | |
size_t avl = mdfy_ring_free(r); | |
if (len > avl) | |
len = avl; | |
if (len == 0) | |
return 0; | |
size_t realHead = (r->head % r->size); | |
size_t realTail = (r->tail % r->size); | |
size_t split = r->size - realHead; | |
if (realHead < realTail || len <= split) { | |
// One copy needed | |
// Head does not wrap around | |
memcpy(r->buf + realHead, data, len * sizeof(float)); | |
} else { | |
// Two copies needed | |
// Head wraps around | |
memcpy(r->buf + realHead, data, split * sizeof(float)); | |
size_t part2 = len - split; | |
memcpy(r->buf, data + split, part2 * sizeof(float)); | |
} | |
size_t newHead = r->head + len; | |
if (newHead >= r->head) { | |
r->head = newHead; | |
} else { | |
// Overflow | |
r->head = realHead + 1; | |
} | |
return len; | |
} | |
void mdfy_ring_push(mdfy_ring_t *r, float v) | |
{ | |
if (mdfy_ring_full(r)) | |
return; | |
r->buf[(r->head % r->size)] = v; | |
r->head = unmasked_inc(r->head, r->size); | |
} | |
float mdfy_ring_pop(mdfy_ring_t *r) | |
{ | |
if (mdfy_ring_empty(r)) | |
return 0.0f / 0.0f; // NaN | |
float v = r->buf[(r->tail % r->size)]; | |
r->tail = unmasked_inc(r->tail, r->size); | |
return v; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef LIBMIDIFY_RINGBUF_H | |
#define LIBMIDIFY_RINGBUF_H | |
#include <unistd.h> | |
struct mdfy_ring_s { | |
float * const buf; | |
const size_t size; | |
size_t head, tail; | |
}; | |
typedef struct mdfy_ring_s mdfy_ring_t; | |
mdfy_ring_t *mdfy_create_ring (size_t); | |
mdfy_ring_t *mdfy_ring_resize (mdfy_ring_t *, size_t newSize); | |
mdfy_ring_t *mdfy_ring_promise (mdfy_ring_t *, size_t minSize); | |
void mdfy_ring_clear (mdfy_ring_t *); | |
void mdfy_destroy_ring (mdfy_ring_t *); | |
_Bool mdfy_ring_empty (mdfy_ring_t *); | |
_Bool mdfy_ring_full (mdfy_ring_t *); | |
size_t mdfy_ring_used (mdfy_ring_t *); | |
size_t mdfy_ring_free (mdfy_ring_t *); | |
size_t mdfy_ring_write (mdfy_ring_t *, float *, size_t len); | |
size_t mdfy_ring_read (mdfy_ring_t *, float *, size_t len); | |
void mdfy_ring_push (mdfy_ring_t *, float); | |
float mdfy_ring_pop (mdfy_ring_t *); | |
#endif //LIBMIDIFY_RINGBUF_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <assert.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include "ringbuf.h" | |
#include "test.h" | |
static void check_alloc(int used); | |
static void array_access(void); | |
static mdfy_ring_t *r; | |
// Could be more efficient | |
// but looks good in logs | |
static void check_alloc(int used) | |
{ | |
if (used == 0) { | |
assert(mdfy_ring_empty(r)); | |
assert(!mdfy_ring_full(r)); | |
assert(mdfy_ring_used(r) == 0); | |
assert(mdfy_ring_free(r) == 5); | |
} else if (used == r->size) { | |
assert(!mdfy_ring_empty(r)); | |
assert(mdfy_ring_full(r)); | |
assert(mdfy_ring_used(r) == r->size); | |
assert(mdfy_ring_free(r) == 0); | |
} else { | |
assert(!mdfy_ring_empty(r)); | |
assert(!mdfy_ring_full(r)); | |
assert(mdfy_ring_used(r) == used); | |
assert(mdfy_ring_free(r) == r->size - used); | |
} | |
} | |
static void array_access(void) | |
{ | |
{ | |
// Write some values | |
float buf[5 * 4] = { 1, 2, 3, 4, 5 }; | |
size_t count = mdfy_ring_write(r, buf, 5 * 4); | |
// Ring should only copy available size | |
assert(count == 5); | |
check_alloc(5); | |
} | |
{ | |
// Read some values | |
float buf[5 * 4] = {}; | |
float correct[5] = {1, 2, 3, 4, 5}; | |
size_t count = mdfy_ring_read(r, buf, 5 * 4); | |
// Ring should only copy available size | |
assert(count == 5); | |
check_alloc(0); | |
// Check values | |
assert(memcmp(buf, correct, 5 * sizeof(float)) == 0); | |
} | |
} | |
int test_ringbuf(void) | |
{ | |
r = mdfy_create_ring(5); | |
check_alloc(0); | |
// Aligned access | |
array_access(); | |
{ | |
mdfy_ring_push(r, 0x0A); | |
// Write one value | |
check_alloc(1); | |
// Read one value | |
assert(mdfy_ring_pop(r) == 0x0A); | |
check_alloc(0); | |
} | |
// Misaligned access (wraparound) | |
array_access(); | |
mdfy_ring_clear(r); | |
check_alloc(0); | |
// Promise already fulfilled | |
{ | |
mdfy_ring_t *newRing = mdfy_ring_promise(r, 5); | |
assert(newRing == r); | |
mdfy_ring_push(r, 1); | |
mdfy_ring_push(r, 2); | |
mdfy_ring_push(r, 3); | |
mdfy_ring_push(r, 4); | |
mdfy_ring_push(r, 5); | |
} | |
// Easy ring copy | |
{ | |
mdfy_ring_t *oldRing = r; | |
mdfy_ring_t *newRing = mdfy_ring_resize(r, 10); | |
assert(newRing != r); | |
r = newRing; | |
check_alloc(5); | |
float correct[] = { 1, 2, 3, 4, 5 }; | |
float buf[5]; | |
mdfy_ring_read(r, buf, 5); | |
// Check values | |
assert(memcmp(correct, buf, 5 * sizeof(float)) == 0); | |
r = oldRing; | |
} | |
// Wraparound ring copy | |
{ | |
r->tail = 1; | |
r->head = 6; | |
mdfy_ring_t *oldRing = r; | |
mdfy_ring_t *newRing = mdfy_ring_resize(r, 10); | |
assert(newRing != r); | |
r = newRing; | |
check_alloc(5); | |
float correct[] = { 2, 3, 4, 5, 1 }; | |
float buf[5]; | |
mdfy_ring_read(r, buf, 5); | |
assert(memcmp(correct, buf, 5 * sizeof(float)) == 0); | |
r = oldRing; | |
} | |
mdfy_destroy_ring(r); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef LIBMIDIFY_TEST_RINGBUF_H | |
#define LIBMIDIFY_TEST_RINGBUF_H | |
int test_ringbuf(void); | |
#endif //LIBMIDIFY_TEST_RINGBUF_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Not yet battle tested and probably has some weird bugs in
read()
andwrite()
.Vanilla C99.
Implemented after the unmasked method in https://www.snellman.net/blog/archive/2016-12-13-ring-buffers/ .
This one should support index overflowing with sizes other than powers of two though.
It uses
floats
as the main data type but that can be changed easily ofc.