单一生产者、单一消费者环形缓冲区的最小限制内存排序?
Least-restrictive memory ordering for single-producer, single-consumer ringbuffer?
我有一个 RingBuffer,它为一个消费者和一个生产者提供服务,并使用两个整数来检测新数据:
_lastReadIndex
_lastWrittenIndex
所以当这两个值不相等时ringbuffer中有未读数据
Producer 增量(和循环缓冲区大小的模数)_lastWrittenIndex
当一个项目被添加到ringbuffer时。
消费者自旋,读取两个值,检查新数据,当有新数据时,它将增量(和模数)_lastReadIndex
.
三个突出显示的术语强调了关于多线程和内存屏障的要求。
考虑到 Intel 的内存模型,我可以将此设计的内存排序放宽到什么程度?我相信英特尔的内存模型允许加载与早期存储重新排序到不同的地址?
使用 C++11 原子库编辑 std::memory_order_xxxx
等
您必须先做几件事:
模数读取和写入点,但保持 _lastReadIndex
和 _lastWrittenIndex
不变,以了解您有多少数据可用,丢失了多少,或者如果它超出 reader 完整周期后。
而且,非常重要的是,尽可能避免共享 - 将 reader 和 writer 变量放在不同的缓存行上。
现在,回答您的问题:
如果您想要实现可移植性,那么您在代码中需要的内存顺序不应考虑架构。标准的原子函数可以解决这个问题。
在递增写入索引之前,您只需要确保缓冲区中的数据可用,这意味着递增时释放语义。
您还必须确保编写器将数据写入内存,而不是优化为仅保留在寄存器中。
newIndex = _lastWrittenIndex+1;
buffer[newIndex % bufSize] = newData;
atomic_store( &_lastWrittenIndex, newIndex, memory_order_release );
在 x86/64 上,这将与:
相同
newIndex = _lastWrittenIndex+1;
buffer[newIndex % bufSize] = newData;
// release semantics means reorder barrier before action:
barrier(); // translates to `asm volatile("":::"memory");`
*(volatile int*)_lastWrittenIndex = newIndex;
当编写访问 _lastWrittenIndex
的代码不超过绝对必要时,如上所示,您不妨将其声明为 volatile,但请记住仍然需要屏障!
我有一个 RingBuffer,它为一个消费者和一个生产者提供服务,并使用两个整数来检测新数据:
_lastReadIndex
_lastWrittenIndex
所以当这两个值不相等时ringbuffer中有未读数据
Producer 增量(和循环缓冲区大小的模数)_lastWrittenIndex
当一个项目被添加到ringbuffer时。
消费者自旋,读取两个值,检查新数据,当有新数据时,它将增量(和模数)_lastReadIndex
.
三个突出显示的术语强调了关于多线程和内存屏障的要求。
考虑到 Intel 的内存模型,我可以将此设计的内存排序放宽到什么程度?我相信英特尔的内存模型允许加载与早期存储重新排序到不同的地址?
使用 C++11 原子库编辑 std::memory_order_xxxx
等
您必须先做几件事:
模数读取和写入点,但保持 _lastReadIndex
和 _lastWrittenIndex
不变,以了解您有多少数据可用,丢失了多少,或者如果它超出 reader 完整周期后。
而且,非常重要的是,尽可能避免共享 - 将 reader 和 writer 变量放在不同的缓存行上。
现在,回答您的问题:
如果您想要实现可移植性,那么您在代码中需要的内存顺序不应考虑架构。标准的原子函数可以解决这个问题。 在递增写入索引之前,您只需要确保缓冲区中的数据可用,这意味着递增时释放语义。 您还必须确保编写器将数据写入内存,而不是优化为仅保留在寄存器中。
newIndex = _lastWrittenIndex+1;
buffer[newIndex % bufSize] = newData;
atomic_store( &_lastWrittenIndex, newIndex, memory_order_release );
在 x86/64 上,这将与:
相同newIndex = _lastWrittenIndex+1;
buffer[newIndex % bufSize] = newData;
// release semantics means reorder barrier before action:
barrier(); // translates to `asm volatile("":::"memory");`
*(volatile int*)_lastWrittenIndex = newIndex;
当编写访问 _lastWrittenIndex
的代码不超过绝对必要时,如上所示,您不妨将其声明为 volatile,但请记住仍然需要屏障!