ava*_*tli 2 c x86 intel memory-alignment cpu-cache
我有一个问题需要了解是否有更好的解决方案。我编写了以下代码,将一些变量从编写器线程传递到读取器线程。这些线程固定到共享相同 L2 缓存的不同 CPU(禁用超线程)。
writer_thread.h
struct a_few_vars {
uint32_t x1;
uint32_t x2;
uint64_t x3;
uint64_t x4;
} __attribute__((aligned(64)));
volatile uint32_t head;
struct a_few_vars xxx[UINT16_MAX] __attribute__((aligned(64)));
Run Code Online (Sandbox Code Playgroud)
reader_thread.h
uint32_t tail;
struct a_few_vars *p_xxx;
Run Code Online (Sandbox Code Playgroud)
写入线程增加头变量,读取线程检查头变量和尾变量是否相等。如果它们不相等,则按如下方式读取新数据
while (true) {
if (tail != head) {
.. process xxx[head] ..
.. update tail ..
}
}
Run Code Online (Sandbox Code Playgroud)
性能是迄今为止最重要的问题。我使用的是 Intel Xeon 处理器,读取器线程每次都会从内存中获取 head 值和 xxx[head] 数据。我使用对齐数组来实现无锁
就我而言,是否有任何方法可以尽快将变量刷新到读取器CPU缓存中。我可以从写入器 CPU 触发读取器 CPU 的预取吗?如果存在的话,我可以使用 __asm__ 来使用特殊的英特尔指令。总之,在固定到不同 CPU 的线程之间传递结构中的变量的最快方法是什么?
提前致谢
volatile根据 C11 的说法, 一个线程写入变量而另一个线程读取变量是未定义的行为。volatile访问也不会相对于其他访问进行排序。您希望atomic_store_explicit(&head, new_value, memory_order_release)在编写器和atomic_load_explicit(&head, memory_order_acquire)读取器中创建 acq/rel 同步,并强制编译器使结构中的存储在向head读取器指示有新数据的存储之前可见。
(tail是读取器线程私有的,因此写入器没有机制可以在写入更多数据之前等待读取器看到新数据。因此,从技术上讲,如果写入器线程在读取器仍在运行时再次写入,则结构内容可能会出现竞争阅读。所以结构也应该是_Atomic)。
您可能需要一个序列锁,其中写入器更新序列号,而读取器在复制出变量之前和之后检查它。 https://en.wikipedia.org/wiki/Seqlock 这可以让您在极少数情况下检测并重试,即当读取器复制数据时写入器正在进行更新。
这对于只写/只读情况非常有用,特别是当您不需要担心读者错过更新时。
请参阅我在 C++11 中对 SeqLock 的尝试:使用 32 位原子实现 64 位原子计数器以及如何使用 c++11 原子库实现 seqlock 锁
GCC使用“memory_order_seq_cst”在负载上重新排序。这是允许的吗?显示另一个示例(此示例会导致 gcc 错误)。
将它们从 C++11 std::atomic 移植到C11 stdatomic应该很简单。请务必使用atomic_store_explicit,因为普通的默认内存排序atomic_store是memory_order_seq_cst哪个速度较慢。
实际上,您无能为力,无法加快作家使其商店在全球范围内可见的速度。CPU 核心已经尽快将存储从其存储缓冲区提交到其 L1d(遵守 x86 内存模型的限制,该模型不允许 StoreStore 重新排序)。
在 Xeon 上,请参阅CPU 何时将存储缓冲区中的值刷新到 L1 缓存?有关不同侦听模式及其对单个套接字内核心间延迟的影响的一些信息。
多核上的缓存是一致的,使用MESI来保持一致性。
对原子变量进行自旋等待的读取器可能是您能做的最好的事情,_mm_pause()在自旋循环内部使用以避免退出自旋循环时清除内存顺序错误推测管道。
您也不希望在写入过程中醒来并重试。您可能希望将 seq-lock 计数器与数据放在同一缓存行中,因此这些存储有望合并到写入核心的存储缓冲区中。
| 归档时间: |
|
| 查看次数: |
246 次 |
| 最近记录: |