如何在循环缓冲区中从头到尾迭代而不分支
Posted
技术标签:
【中文标题】如何在循环缓冲区中从头到尾迭代而不分支【英文标题】:How to iterate from head to tail in circular buffer without branching 【发布时间】:2020-04-14 02:47:24 【问题描述】:我有以下问题,尽管有很多方法都无法满足我的需求:
拥有一个索引为size_t
的循环缓冲区,而底层缓冲区可能是我喜欢的任何东西,我需要像这样向前和向后迭代:
从头到尾,从尾到头(换句话说,我需要能够遍历此缓冲区中的所有单元格)。
我有以下功能:
size_t prevIdxCircularBuffer(size_t head, size_t size, size_t capacity, size_t idx)
const size_t sizeWithGuard = size + 1;
const size_t prevOffset = (idx + capacity - 1 - head) % capacity;
const size_t sizeCorrectedPrevOffset = prevOffset % sizeWithGuard;
return (head + sizeCorrectedPrevOffset) % capacity;
size_t nextIdxCircularBuffer(size_t head, size_t size, size_t capacity, size_t idx)
const size_t sizeWithGuard = size + 1;
const size_t nextOffset = (idx + capacity + 1 - head) % capacity;
const size_t sizeCorrectedNextOffset = nextOffset % sizeWithGuard;
return (head + sizeCorrectedNextOffset) % capacity;
小说明:
sizeWithGuard
是大小加尾部“守卫”元素(尾部为空)。
虽然 next 工作正常,但当 idx == head 时,previous 总是无法从头到尾迭代(它总是导致capacity - 1
)。我正在寻找有关如何更改此算法以使其始终工作的线索。
对我来说至关重要的是索引必须是size_t
类型并且代码中没有分支(否则这个问题将不存在:)
【问题讨论】:
【参考方案1】:诀窍在于有两种类型的索引:列出索引,范围从 0 到 size - 1(或者,在您的情况下,为 0 到 size 与尾部保护)和 缓冲区索引 范围从 0 到 容量 - 1。我建议稍微更改代码以澄清这一点。
size_t prevIdxCircularBuffer(size_t head, size_t size, size_t capacity, size_t idx)
// at the beginning, idx is a buffer index
const size_t sizeWithGuard = size + 1;
// listIdx is the same index, just translated to a list index
const size_t listIdx = (idx + capacity - head) % capacity;
// sizeCorrectedPrevOffset is the list index of the previous element
// (ranging from 0 to size inclusively)
const size_t sizeCorrectedPrevOffset = (prevOffset + sizeWithGuard - 1) % sizeWithGuard;
// finally convert sizeCorrectedPrevOffset back to a buffer index
// and return it
return (head + sizeCorrectedPrevOffset) % capacity;
所以我改变的是:
-
从 prevOffset 中删除了 -1 减法。将该变量重命名为 listIdx,因为它不再是“以前的”。
在 sizeCorrectedPrevOffset 中包含 -1 减法(还添加了 sizeWithGuard,因此我们保持在界限内)。
有什么区别?这是一个例子:
tail head=idx
+-----------+-----------------------------+---------------+
| | | |
+-----------+-----------------------------+---------------+
0 8 20 36
capacity=37
head=20
idx=20
size=25+1 guard element, spanning positions [20-36] and [0-8]
我的代码版本是这样的:
listIdx = (20 + 37 - 20) % 27 = 0:正确地实现了它的列表索引为0(头部); sizeCorrectedPrevOffset = (0 + 26 - 1) % 26 = 25:新的列表索引应该是25(尾部); returns (20 + 25) % 37 = 8:尾元素的缓冲区索引相比之下,您的代码可以:
prevOffset = (20 + 37 - 1 - 20) % 37 = 36 % 37 = 36 从这里开始偏离轨道。【讨论】:
以上是关于如何在循环缓冲区中从头到尾迭代而不分支的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin 协程Channel 通道 ② ( Channel 通道容量 | Channel 通道迭代 | 使用 iterator 迭代器进行迭代 | 使用 for in 循环进行迭代 )
Kotlin 协程Channel 通道 ② ( Channel 通道容量 | Channel 通道迭代 | 使用 iterator 迭代器进行迭代 | 使用 for in 循环进行迭代 )