News for hackers. Stuff that matters.
Re:I've been writing ring buffers wrong all these years by jsnell on Wednesday December 14, 2016 @10:08AM (#13176434)
It's not just an optimization, it's necessary for correct operation. With a non-power of two buffer the integer wraparound causes a discontinuity.
Re:I've been writing ring buffers wrong all these years by heisenbit on Wednesday December 14, 2016 @11:32AM (#13177244)
As far as I can tell that int wrap around could be avoided by

- subtracting buffer size from both pointers once the read pointer has wrapped.

- choosing a longer int for the math operation where possible

That seems a small price for the freedom to be able to choose an appropriate buffer size.

Re:I've been writing ring buffers wrong all these years by mark-r on Wednesday December 14, 2016 @01:40PM (#13178473)
One of the benefits of the original algorithm is the independence of the read and write indexes, they can be updated from different threads (or different processors!) without any atomic operations beyond writing or reading a value. Subtracting from both pointers requires an additional atomic read/modify/write operation.
Re:I've been writing ring buffers wrong all these years by Guvante on Wednesday December 14, 2016 @03:00PM (#13179153)
You could also just restrict the pointers in the normal way but to two times the size of the buffer. So instead of wrapping at N you wrap at 2*N.

You are only encoding 1 bit of data (first or second) so adding more data than that by allowing unsigned integer overflow is just an optimization, not fundamentally necessary.

Re:I've been writing ring buffers wrong all these years by mark-r on Wednesday December 14, 2016 @03:31PM (#13179382)
If you do that then the size() function becomes a problem. The original implementation relies on unsigned integer wrap-around to give the proper result when write < read.
Re:I've been writing ring buffers wrong all these years by caf on Wednesday December 14, 2016 @06:55PM (#13180936)
That is fairly easy to fix, though. Add N to the size value you get until its non-negative.
Re:I've been writing ring buffers wrong all these years by amag on Wednesday December 14, 2016 @04:48PM (#13179973)
It all depends on your use case. In my experience, most of the times when dealing with RB:s, it's more important to guarantee that the read index and the write index can be updated lock-free by different threads (e.g. one producer and one consumer) than to have a very specific non-power-of-2 capacity.
Re:I've been writing ring buffers wrong all these years by jgrahamc on Wednesday December 14, 2016 @10:15AM (#13176504)
No, it's an optimization.
Re:I've been writing ring buffers wrong all these years by dllthomas on Wednesday December 14, 2016 @10:51AM (#13176851)
No, it's not. A non-power-of-two will lead to incorrect behavior, implementing the data structure the way the author describes, for precisely the reason given by your parent comment. There are other implementations that don't suffer from this (described in the comments there), but it's not simply a matter of replacing bitwise-and with a more generic computation of the modulus.

Imagine we had four bit integers and a three cell array. Stepping from 7 (= 1 mod 3) to 8 (= 2 mod 3) winds up stepping instead to 0 (= 0 mod 3) because overflow, which would reuse cells inappropriately.

Re:I've been writing ring buffers wrong all these years by jgrahamc on Wednesday December 14, 2016 @10:57AM (#13176893)
I never claimed you should use a modulus operation.
Re:I've been writing ring buffers wrong all these years by dllthomas on Wednesday December 14, 2016 @11:07AM (#13176999)
You said,

> It's unclear to me why the focus on a 2^n sized buffer just so you can use & for the mask.

In fact it is required for correctness to use the approach he specified with any choice of masking operation.

Re:I've been writing ring buffers wrong all these years by jgrahamc on Wednesday December 14, 2016 @11:13AM (#13177062)
Any choice?

He only ever calls mask() after an increment. There are other ways of detecting the overflow and wrap to 0 that don't involve modulus.

Re:I've been writing ring buffers wrong all these years by dllthomas on Wednesday December 14, 2016 @11:19AM (#13177126)
Yes, any choice. Yes, that's not the case if you change the implementation in other ways in tandem - but then you are describing a different implementation. To mask where he does, any choice of mask will exhibit this problem (or other problems), mathematically. I can produce a proof if you need it.

> He only ever calls mask() after an increment.

I think you misunderstand what he's talking about. He calls mask at insert and lookup and he does not store the masked value - which leaves the implementation vulnerable to overflow (which is not a problem iff the array is 2^n big).

Re:I've been writing ring buffers wrong all these years by simcop2387 on Wednesday December 14, 2016 @01:37PM (#13178441)
That said, one of the comments on the blog actually had a great suggestion for dealing with this. Wrap the value yourself at 2*capacity, you'll still get the same benefits from the algorithm and it's trivial to prevent the overflow then. You can then avoid the modulo operation (subtract the capacity if it's a larger index) and get better performing non-power of 2 capacities.

That said I'd mostly be using this kind of thing in a situation where i'd want to have a power of two sized buffer anyway.

Re:I've been writing ring buffers wrong all these years by dllthomas on Wednesday December 14, 2016 @02:56PM (#13179133)
Yeah, that's a great option!

Conceptually it's roughly what's going on anyway - something must wrap at some point if we're going to store our offsets in limited space - just that we get the wrapping for free from overflow if it's 2^n.

The confusion above stemmed, I think, from the fact that in the "original" implementation the mask is used for that wrapping and then we have a noop projection from offset to index. In this implementation, overflow is used for that wrapping and the mask is to project from offset to index. In the implementation you discuss, we pick still other functions for both.

Re:I've been writing ring buffers wrong all these years by emn13 on Thursday December 15, 2016 @04:35AM (#13183459)
fastest would likely be to wrap at the greatest multiple of N that fits in your integer representation, since that (probably dramatically) reduces branch mispredicts.

However, you're still doing lots of modulos to then find the "real" array index from the "virtual" one, so this is still likely not a great option compared to the power-of-two buffers.

It might actually be faster (at least for mildly large buffers) to use 2 ifs and a range that's twice the capacity. One ifs checks whether your virtual index needs to subtract the capacity to become real, and another checks whether it's time to substract 2 times the capacity.

Re:I've been writing ring buffers wrong all these years by dllthomas on Thursday December 15, 2016 @06:35PM (#13189746)
With the conditional subtraction recommended elsewhere, you can do 2*N with no branch mispredictions and no modulo. Whether that's actually faster should be tested, if you're in an environment where you care about such things.
Re:I've been writing ring buffers wrong all these years by jgrahamc on Wednesday December 14, 2016 @11:26AM (#13177187)
A ha! That is the point I was missing. Thanks.
Re:I've been writing ring buffers wrong all these years by falcolas on Wednesday December 14, 2016 @11:07AM (#13177000)
Array size of 5 produces a mask of 0100

Write index of 8 = 1000

Masking those together to create a write position 1000 & 0100 = 0

Doesn't work out correctly, got 0, would expect to get 2. In fact, you could never get a write position of 1, 2, or 3 with an array size of 5.

Re:I've been writing ring buffers wrong all these years by dllthomas on Wednesday December 14, 2016 @11:09AM (#13177020)
This is not the problem.

You are assuming still using &, which is very obviously incorrect with a very obvious fix (%5) which is still wrong because of behavior at overflow.

Re:I've been writing ring buffers wrong all these years by falcolas on Wednesday December 14, 2016 @11:47AM (#13177372)
I'm answering the specific problem posed by the OP, given his other comments in this thread.

[EDIT] Resolved internal concerns about size calculations.

Re:I've been writing ring buffers wrong all these years by dllthomas on Wednesday December 14, 2016 @12:35PM (#13177857)
You might have misread the OP. He's not asking why using & for the mask requires a 2^n sized buffer. He's asking why bother using & when it imposes these additional constraints on us. A part of the answer is that the constraints are already there with this approach, even if you pick a different function for masking than f(i,n) = i & (n - 1) -- which point OP only recently understood due to a misreading of the article.
Re:I've been writing ring buffers wrong all these years by brianon99 on Wednesday December 14, 2016 @10:50AM (#13176841)
This is not only an optimisation. Power of 2 is necessary to avoid discontinuity. See the comments below the article for explanation.