6

I have a fixed size circular buffer (implemented as an array): upon initialization, the buffer gets filled with the specified maximum number of elements which allows the use of a single position index in order to keep track of our current position in the circle.

What is an efficient way to access an element in the circular buffer? Here is my current solution:

int GetElement(int index)
{
    if (index >= buffer_size || index < 0)
    {
        // some code to handle the case
    }
    else
    {
        // wrap the index
        index = end_index + index >= buffer_size ? (index + end_index) - buffer_size : end_index + index;
    }

    return buffer[index];
}

Some definitions:
end_index is the index of the element immediately after the last element in the circle (it would also be considered the same as the start_index, or the first element of the circle).
buffer_size is the maximum size of the buffer.

Kiril
  • 39,672
  • 31
  • 167
  • 226
  • When isn't end_index equal to buffer_size? – Fred Nurk Feb 02 '11 at 09:42
  • @Fred, when you add an element to the circular buffer... the index wraps every time you go past the buffer_size. – Kiril Feb 02 '11 at 18:14
  • @Lirik: I must be missing something. – Fred Nurk Feb 02 '11 at 18:20
  • @Fred, the gist of a [Circular Buffer](http://en.wikipedia.org/wiki/Circular_buffer) is that your new data overwrites your old data and you need to keep track of where you are in the circle. It uses an array to mimic a circle, so end_index tells us the position of the last element (or the element after the last element). The buffer_size might be 100, but we only have 13 elements, therefore end_index is 14. If I already have 100 elements, the end_index is 99 and if I add one more element, the end_index will wrap around to 0. Mine works like a queue, except I have O(1) access to every element. – Kiril Feb 02 '11 at 18:49
  • @Lirik: Ah, so the buffer isn't always filled? I thought it was from "the buffer gets filled with the specified maximum number of elements". – Fred Nurk Feb 02 '11 at 18:53
  • @Fred, The buffer is **always** filled: in the beginning it gets filled and then I can add more items later (which I do). I fill it in the beginning so that I don't need to keep a separate index for the start of the circle and the end of the circle. If I don't fill it up right away, then I will have a partially filled circle and I will need two indexes to iterate it. All of these cases are shown in the wiki page of the Circular Buffer. – Kiril Feb 02 '11 at 20:16
  • If you "don't need to keep a separate index for the start of the circle and the end of the circle", then what is end_index? Didn't you say it is "the same as the start_index, or the first element of the circle"? – Fred Nurk Feb 02 '11 at 20:26
  • @Fred end_index is my poor vocabulary. I didn't want to call it start_end_index (feels somewhat strange) and I couldn't think of a good way to describe it, but it does serve as **both** the start and the end index. If the buffer is always full (which my implementation guarantees), then the start and the end index are always the same so there is no need to keep two separate variables. – Kiril Feb 02 '11 at 20:42
  • @Lirik: Why don't you just set the start to a known value, like 0, so you don't need a variable for it and it simplifies the modulus math for wrapping around? – Fred Nurk Feb 02 '11 at 20:43
  • @Fred: are you suggesting the use of a "token" or a "marker" item in the circle or something? It may be a good idea, but how would I get the n-th item in the circle (i.e. don't I need a reference to where start is)? Do I move the start when I add another item in the buffer? – Kiril Feb 02 '11 at 21:29
  • Not a sentinel. I think I'm still misunderstanding you, but I'm going to leave it here. I do know how to write a circular buffer :), but I'm just missing something about how you're doing it that I can't pick up in the limited space of comments. – Fred Nurk Feb 02 '11 at 21:39
  • @Fred: I'm sorry, I feel like I'm probably not explaining it in the best way, but thanks for your time. – Kiril Feb 02 '11 at 21:54

6 Answers6

19

Best I've come up with is:

public static int Wrap(int index, int n)
{
    return ((index % n) + n) % n;
}

(Assuming you need to work with negative numbers)

John Demetriou
  • 4,093
  • 6
  • 52
  • 88
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 2
    It wasn't obvious to me so here is a slight clarification: `a` is the index that needs wrapped and `n` is the array size. – GER Jan 06 '17 at 16:59
  • 2
    I just got bitten by a bug when using code like this. The problem is that if index is close to the size of the int, this can overflow and return a negative number. For example: `((872415600 % 1275068416) + 1275068416) % 1275068416)` is equal to `-872414864`. – user545424 May 24 '18 at 19:51
11

Ensure that the buffer is always a power of two long and mask out the top bits.

Peter Taylor
  • 4,918
  • 1
  • 34
  • 59
  • 4
    @Johnatan: A prematurely optimized modulo operation, yes. –  Feb 01 '11 at 21:35
  • @delnan I don't know about prematurely. While personally I'd use modulo, it's actually very common practice to have arrays be powers of two for this reason. – corsiKa Feb 01 '11 at 22:14
  • @Jonathan, what he's doing at the moment is already prematurely optimised modulo. I'm just suggesting the logical extreme. :P – Peter Taylor Feb 01 '11 at 22:19
  • 1
    I tested the modulus version vs the top-bit-mask and they're both equal. It seems that the modulus is more robust since it doesn't require the `buffer_size` to be a power of 2. – Kiril Feb 05 '11 at 07:10
6

I tested all 3 versions:

// plain wrap
public static int WrapIndex(int index, int endIndex, int maxSize)
{
    return (endIndex + index) > maxSize ? (endIndex + index) - maxSize : endIndex + index;
}

// wrap using mod
public static int WrapIndexMod(int index, int endIndex, int maxSize)
{
    return (endIndex + index) % maxSize;
}

// wrap by masking out the top bits
public static int WrapIndexMask(int index, int endIndex, int maxSize)
{
    return (endIndex + index) & (maxSize - 1);
}

The performance results (ticks):

Plain: 25 Mod: 16 Mask: 16 (maxSize = 512)
Plain: 25 Mod: 17 Mask: 17 (maxSize = 1024)
Plain: 25 Mod: 17 Mask: 17 (maxSize = 4096)

So it seems that the modulus is the better choice, because it does not require any restriction on the size of the buffer.

Community
  • 1
  • 1
Kiril
  • 39,672
  • 31
  • 167
  • 226
  • This was helpful, I'm wondering how the double mod solution that works with negative numbers / decrementing (https://stackoverflow.com/a/10184756/115751) performs versus a ternary like `( endIndex = 0 ? maxSize: endIndex ) - 1;` – WilliamKF Mar 02 '21 at 23:26
  • Modulo operation is inefficient because it is actually division. Most processors (at least microcontrollers and high-end DSPs) do not have division operation, so it has to be implemented with series of additions, shifts and comparisons. Even if the processor has division instruction, it is usually implemented with microcode and takes dozens of times longer than masking. Compiler may optimize modulo into masking if maxSize is power of two, but you can not rely on that. – PauliL Mar 16 '22 at 13:27
  • @WilliamKF: Ternary operation is just a cryptic way to write if...else clause. It is not more efficient but it is more difficult to read. Generally, you should avoid using it. – PauliL Mar 16 '22 at 13:35
5

It'll depend somewhat on the processor, but it's probably at least worth trying something like return (end_index + index) % buffer_size;

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • **(1)** Is `end_index` not equal to `buffer_size` (assuming a 0-indexed array)? **(2)** With that in mind, let's simplify this to `return (a + n) % n;` -- then if `a = -5`, and `n = 4`, then `(a+n)` yields `-1`, which is still out of bounds. (i.e., it fails for `a < -n`). – mpen Apr 17 '12 at 03:24
  • 1
    @Mark: although the original code implies the possibility of negative numbers, that's normally just not allowed at all (though it's probably better to use an unsigned type to ensure against it). – Jerry Coffin Apr 17 '12 at 03:57
  • Oh... I guess my situation is different. I often use -1 to mean the last element. – mpen Apr 17 '12 at 03:59
  • @Mark: in that case, you'd just about have to clamp the value a bit differently, something like: ` int clamp(int val, int max) { while (val < 0) val += max; while (val >= max) val -= max; return val; }` – Jerry Coffin Apr 17 '12 at 04:02
  • I've done it that way in the past (with a while loop), but it'd be more efficient to just mod it twice, no? – mpen Apr 17 '12 at 04:03
  • 1
    @Mark: if your value might be drastically out of bounds, then a remainder is likely to be faster. If you just want to handle something like -max..max*2 (or so), then the while loops are *probably* faster (taking a remainder isn't particularly fast on most processors). – Jerry Coffin Apr 17 '12 at 04:05
4
int GetElement(int index)
{
    return buffer[(end_index + index) % buffer_size];
}

See modulo operation for the more information on the modulus operator (%).

Judge Maygarden
  • 26,961
  • 9
  • 82
  • 99
0

FWIW, you could always do a parallel array: i = next[i];

But, really, I've always just done this: i++; if (i >= n) i = 0; OR i = (i+1) % n;

Regardless, I'd be really surprised if this is ever a significant performance issue.

Mike Dunlavey
  • 40,059
  • 14
  • 91
  • 135