在固定不同CPU的两个线程之间传递几个变量的最佳方法

在固定不同CPU的两个线程之间传递几个变量的最佳方法,c,x86,intel,memory-alignment,cpu-cache,C,X86,Intel,Memory Alignment,Cpu Cache,我有一个问题,我需要了解是否有更好的解决方案。我编写了以下代码,将一些变量从编写器线程传递到读取器线程。这些线程被固定到共享相同二级缓存的不同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; stru

我有一个问题,我需要了解是否有更好的解决方案。我编写了以下代码,将一些变量从编写器线程传递到读取器线程。这些线程被固定到共享相同二级缓存的不同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)));
uint32_t tail;
struct a_few_vars *p_xxx;
reader_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)));
uint32_t tail;
struct a_few_vars *p_xxx;
writer线程增加head变量,reader线程检查head变量和tail是否相等。如果它们不相等,则按如下方式读取新数据

while (true) {
    if (tail != head) {
        .. process xxx[head] ..
        .. update tail ..
    }
}
到目前为止,性能是最重要的问题。我使用的是Intel Xeon处理器,读卡器线程每次都从内存中获取head值和xxx[head]数据。我使用对齐的数组来解除锁定

在我的例子中,是否有任何方法可以尽快将变量刷新到读卡器CPU缓存中。我可以从写入CPU触发读卡器CPU的预取吗。如果存在,我可以使用_asm__使用特殊的英特尔指令。总之,在固定到不同CPU的线程之间传递结构中变量的最快方法是什么


提前感谢

根据C11,当另一个线程读取变量时,一个线程编写一个
volatile
变量是未定义的行为<代码>易失性访问也不会相对于其他访问排序。您需要在写入程序中使用
原子存储\u显式(&head,new\u值,内存\u顺序\u释放)
和在读取器中使用
原子加载\u显式(&head,内存\u顺序\u获取)
来创建acq/rel同步,并强制编译器使结构中的存储在存储到
head
之前可见,这向读者表明有新数据

tail
对于读卡器线程是私有的,因此写卡器没有机制等待读卡器在写入更多数据之前看到新数据。因此从技术上讲,如果写卡器线程在读卡器仍在读取时再次写入,则结构内容上可能存在竞争。因此结构也应该是
\u原子的


<强>您可能需要一个SEQ锁,其中作者在复制变量之前和之后更新序列号,并且读者检查它。< /强>这使得您可以在罕见的情况下检测和重试,当作者在复制数据时,作者处于更新的中间。 它非常适合于只写/只读的情况,尤其是当您不需要担心读卡器丢失更新时

请参阅我在C++11中尝试的SeqLock:以及

并显示了另一个示例(此示例导致gcc错误)

将这些从C++11 std::atomic移植到应该很简单。确保使用显式
原子存储
,因为普通
原子存储
的默认内存顺序是
内存顺序cst
,速度较慢


实际上,您所能做的并不会加快编写器使其存储在全球可见的速度。CPU核心已经尽可能快地将存储从其存储缓冲区提交到其L1d(遵守x86内存模型的限制,该模型不允许存储重新排序)

在Xeon上,请参阅以获取有关不同Snoop模式及其对单个套接字中的内核间延迟的影响的一些信息。

多核上的缓存是一致的,使用MESI来保持一致性

读卡器自旋等待原子变量可能是您能做的最好的方法,在自旋循环内使用
\u mm\u pause()
,以避免退出自旋循环时内存顺序错误


<>你也不想在写作过程中醒来,必须重试。您可能希望将seq lock计数器与数据放在同一缓存线中,这样这些存储有望合并到写入内核的存储缓冲区中。

volatile
不足以防止争用情况。您需要一个互斥锁,或者您必须通过
stdatomic.h
@CraigEstey中的原语访问变量,这是不正确的,因为writer线程首先更新xxx[head+1],然后更新head,这样就足以防止争用情况。您使用的是旧Core 2 Xeon吗?事实上,这些没有HT,所以必须是奔腾4 Xeon,才能让你的问题变得有意义。所有较新的Intel CPU(Nehalem及更新版本)都具有专用的每核L1和L2,并且仅共享最后一级的L3缓存。这包括KNL(Xeon Phi)。@PeterCordes,你是对的,我写了相同的二级缓存来强调没有任何NUMA事务。CPU在同一个处理器中。@CraigEstey:正确的方法是使用C11
原子存储\显式(…,内存\顺序\释放)
。在x86上,它编译为一个
mov
存储。在ARM上,它将编译为存储+
dsb ish
。在AArch64上,它将编译成一个
stra
或其他任何名称,一个顺序一致性发布存储,但没有障碍。您不想在C中手动使用
\u mm_mfence()
之类的东西来定位asm内存模型,因为C11 stdatomic的全部目的是让您编写可移植代码,并让编译器为您完成这一任务。不过,知道什么是有效的还是很好的。就个人而言,我喜欢一个票锁,以防止饥饿和促进公平:所有关于限制旋转的其他内容可能都适用。从页面上看,我认为
now\u服务的旋转可以通过简单的fetch(即非原子)完成,因为只有
fetch\u和_inc
需要原子。我在运输产品的生产代码中使用过这个。@CraigEstey:它必须是一个原子提取,例如
原子提取\u显式(&now\u server,memory\u order\u acquire)
。它不是读修改写,但它必须是原子的,否则可能会有值撕裂。(在C语言中,编译器需要假定其他线程