diff options
author | Yorhel <git@yorhel.nl> | 2012-11-10 15:43:25 +0100 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2012-11-10 15:43:25 +0100 |
commit | d0618b33d654c714c620cc6a8849594c1adf6706 (patch) | |
tree | 0c24e32d3eef2b2484bc7ff0e78abc06635b425b | |
parent | fba3368ae2b139eee24d3d54f9036438fe6a2502 (diff) |
Added ecbuf - An efficient expanding circular buffer
-rw-r--r-- | README | 1 | ||||
-rw-r--r-- | ecbuf.h | 149 |
2 files changed, 150 insertions, 0 deletions
@@ -2,6 +2,7 @@ This git repository holds a collection of small and independent C libraries. dbusev - Register a DBusConnection with libev dbusuv - Register a DBusConnection with libuv + ecbuf - An efficient expanding circular buffer ylog - A small, flexible and efficient logging system for C yopt - A small, simple and portable getopt_long() replacement yuri - A minimal URI validation and parsing library @@ -0,0 +1,149 @@ +/* Copyright (c) 2012 Yoran Heling + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* This is an efficient circular buffer implementation that automatically + * expands the buffer when it's full. As such, it behaves like any other FIFO + * queue. + * + * Usage: + * + * ecbuf_t(int) queue; + * ecbuf_init(queue); + * + * // Writing + * ecbuf_push(queue, 3); + * ecbuf_push(queue, 5); + * ecbuf_push(queue, 7); + * ecbuf_push(queue, 11); + * + * // = 11 + * printf("Most recently queued = %d\n", ecbuf_pry(queue)); + * // = 3 + * printf("Least recently queued = %d\n", ecbuf_peek(queue)); + * // = 4 + * printf("Queue length = %d\n", ecbuf_len(queue)); + * + * // Reading + * while(!ecbuf_empty(queue)) { + * printf("Item: %d\n", ecbuf_pop(queue)); + * } + * + * ecbuf_destroy(queue); + * + * + * The concept is explained at + * http://blog.labix.org/2010/12/23/efficient-algorithm-for-expanding-circular-buffers + * + * This implementation is slightly different, in that it provides a macro to + * read back the most recently written value without adding more overhead in + * terms of the number of variables to keep track of. This implementation does + * leave at least one slot empty, so in terms of memory usage, it doesn't + * really matter (depending on the pointer size of your machine and the size of + * your elements). + */ + +#ifndef ECBUF_H +#define ECBUF_H + +#include <stdlib.h> + + +/* The variables are: + * i: Index we're going to write to in the next push() + * li: Last index we've written to in the last push() + * o: Index we're going to read from in the next pop() + * b: Index of the last written item before the buffer has been expanded. + * cm: Maximum index of the circular buffer. (i.e. size of circular buffer -1) + * bm: Maximum index of the complete buffer. (i.e. size of complete buffer -1) + */ +#define ecbuf_t(type) struct {\ + int i, li, o, b, cm, bm;\ + type *a;\ + } + +/* Internal type for use with ecbuf__pop() */ +typedef ecbuf_t(void) ecbuf__t; + + +#define ecbuf_init(v) do {\ + (v).bm = (v).cm = 31;\ + (v).o = (v).i = 0;\ + (v).b = -1;\ + (v).a = malloc(((v).bm+1)*sizeof(*(v).a));\ + } while(0) + +#define ecbuf_destroy(v) free((v).a) + +/* Number of items queued. The first macro is our initial implementation and + * covers each case separately, the latter one is an optimization with only a + * single branch. Both *should* be correct, but I'm keeping the old one for + * reference. */ +#define ecbuf__len_ref(v) ((v).cm == (v).bm\ + ? ((v).i >= (v).o ? (v).i - (v).o : (v).cm - (v).o + (v).i + 1)\ + : ((v).b >= (v).o ? (v).b - (v).o + (v).i - (v).cm : (v).b - (v).o + (v).i + 1)) + +#define ecbuf_len(v) (((v).i - (v).o + (v).b + 1 + ((v).b >= (v).o ? -1 - (v).cm : (v).bm + 1)) & (v).bm) + + +/* A faster version of ecbuf_len(v) == 0 */ +#define ecbuf_empty(v) ((v).i == (v).o) + +/* Peek into the queue, requires !ecbuf_empty(v) */ +#define ecbuf_peek(v) ((v).a[(v).o]) + +/* Similar to peek, but reads back the most *recently* queued item. + * (I'm bad at naming, I know) */ +#define ecbuf_pry(v) ((v).a[(v).li]) + +#define ecbuf_push(v, x) do {\ + (v).a[(v).i] = (x);\ + (v).li = (v).i;\ + if((v).i == (v).o-1 || ((v).i == (v).cm && (v).o == 0) || ((v).i == (v).bm && (v).bm != (v).cm)) {\ + if((v).i == (v).o-1)\ + (v).b = (v).i;\ + (v).i = (v).bm+1;\ + (v).bm = ((v).bm+1)*2-1;\ + (v).a = realloc((v).a, ((v).bm+1)*sizeof(*(v).a));\ + if((v).li == (v).cm && (v).o == 0)\ + (v).cm = (v).bm;\ + } else\ + (v).i = (v).i == (v).bm ? 0 : (v).i+1;\ + } while(0) + + +static inline int ecbuf__pop(ecbuf__t *v) { + int l = v->o; + if(v->o == v->cm) + v->o = 0; + else if(v->o == v->b) { + v->o = v->cm + 1; + v->b = -1; + v->cm = v->bm; + } else + v->o++; + return l; +} +#define ecbuf_pop(v) ((v).a[ecbuf__pop((ecbuf__t *)&(v))]) + +#endif + +/* vim: set noet sw=4 ts=4: */ |