There is a trick to get rid of the wrap-around test. Use modular arithmetic (best if ring size is a power of 2, then even when the integer wraps everything still works):
next_write_idx = write_idx + 1;
if (next_write_idx == read_idx) return full;
ring[write_idx & (RING_SIZE - 1)] = data;
write_idx = next_write_idx;
This eliminates one possible incorrectly predicted branch and allows you to use all slots of the ring. The original code always leaves one slot empty to avoid the ambiguity between empty and completely full (where in both cases, the indexes have the same value). This could matter for small rings.The way to think of the indexes: they hold the number of items written to or read from the ring since reset.
I guess a similar optimization to the cached entries is to pop as many items as possible before returning:
local_write_idx = write_idx;
local_read_idx = read_idx;
while (local_read_idx != local_write_idx)
process(ring[local_read_idx++] & (RING_SIZE - 1)]);
read_idx = local_read_idx;
return;
This has other advantages: the ring read is a tight loop and you are repeatedly calling "process"- the working set (the cache and branch predictors) will be all set up for it after the first call.