在固定不同 CPU 的 2 个线程之间传递一些变量的最佳方法
Optimal way to pass a few variables between 2 threads pinning different CPUs
我有一个问题需要了解是否有更好的解决方案。我编写了以下代码以将一些变量从编写器线程传递到 reader 线程。这些线程固定到不同的 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)));
reader_thread.h
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 处理器,reader 线程每次都从内存中获取 head 值和 xxx[head] 数据。我使用对齐的数组是无锁的
在我的例子中,有什么方法可以尽快将变量刷新到 reader CPU 缓存中。我可以从作者 CPU 触发对 reader CPU 的预取吗?如果存在,我可以使用 __asm__ 的特殊英特尔指令。总之,在固定到不同 CPU 的线程之间传递结构中变量的最快方法是什么?
提前致谢
根据 C11,一个线程写入 volatile
变量而另一个线程读取它是未定义的行为。 volatile
访问也没有相对于其他访问排序。您希望编写器中的 atomic_store_explicit(&head, new_value, memory_order_release)
和 reader 中的 atomic_load_explicit(&head, memory_order_acquire)
创建 acq/rel 同步,并强制编译器在存储到 [= 之前使存储到结构中可见14=] 指示 reader 有新数据。
(tail
对 reader 线程是私有的,因此没有机制让作者等待 reader 在写入更多数据之前看到新数据。所以从技术上讲有如果编写器线程在 reader 仍在读取时再次写入,则结构内容可能存在竞争。因此结构也应为 _Atomic
).
您可能需要一个序列锁,其中作者更新序列号并且 reader 在复制出变量之后 和 之前检查它. https://en.wikipedia.org/wiki/Seqlock 这让您可以在 reader 复制数据时作者正处于更新中间的极少数情况下检测并重试。
它非常适合只写/只读情况,特别是如果您不需要担心 reader 丢失更新。
查看我在 C++11 中对 SeqLock 的尝试: and also how to implement a seqlock lock using c++11 atomic library
并且 GCC reordering up across load with `memory_order_seq_cst`. Is this allowed? 显示了另一个例子(这个例子导致了一个 gcc 错误)。
将这些从 C++11 std::atomic 移植到 C11 stdatomic 应该很简单。确保使用 atomic_store_explicit
,因为普通 atomic_store
的默认内存顺序是 memory_order_seq_cst
,速度较慢。
实际上,您无能为力地加快编写器使其商店在全球可见的速度。 CPU 内核已经尽快将存储从其存储缓冲区提交到其 L1d(遵守 x86 内存模型的限制,该模型不允许 StoreStore 重新排序)。
在 Xeon 上,请参阅 了解有关不同侦听模式及其对单个插槽内内核间延迟的影响的一些信息。
多核上的缓存是一致的,使用MESI保持一致性。
A reader 原子变量上的自旋等待可能是你能做的最好的,在自旋循环内使用 _mm_pause()
以避免在退出时清除内存顺序错误推测管道自旋环。
您也不希望在写入过程中醒来而不得不重试。您可能希望将 seq-lock 计数器放在与数据相同的缓存行中,因此这些存储有望合并到写入核心的存储缓冲区中。
我有一个问题需要了解是否有更好的解决方案。我编写了以下代码以将一些变量从编写器线程传递到 reader 线程。这些线程固定到不同的 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)));
reader_thread.h
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 处理器,reader 线程每次都从内存中获取 head 值和 xxx[head] 数据。我使用对齐的数组是无锁的
在我的例子中,有什么方法可以尽快将变量刷新到 reader CPU 缓存中。我可以从作者 CPU 触发对 reader CPU 的预取吗?如果存在,我可以使用 __asm__ 的特殊英特尔指令。总之,在固定到不同 CPU 的线程之间传递结构中变量的最快方法是什么?
提前致谢
根据 C11,一个线程写入 volatile
变量而另一个线程读取它是未定义的行为。 volatile
访问也没有相对于其他访问排序。您希望编写器中的 atomic_store_explicit(&head, new_value, memory_order_release)
和 reader 中的 atomic_load_explicit(&head, memory_order_acquire)
创建 acq/rel 同步,并强制编译器在存储到 [= 之前使存储到结构中可见14=] 指示 reader 有新数据。
(tail
对 reader 线程是私有的,因此没有机制让作者等待 reader 在写入更多数据之前看到新数据。所以从技术上讲有如果编写器线程在 reader 仍在读取时再次写入,则结构内容可能存在竞争。因此结构也应为 _Atomic
).
您可能需要一个序列锁,其中作者更新序列号并且 reader 在复制出变量之后 和 之前检查它. https://en.wikipedia.org/wiki/Seqlock 这让您可以在 reader 复制数据时作者正处于更新中间的极少数情况下检测并重试。
它非常适合只写/只读情况,特别是如果您不需要担心 reader 丢失更新。
查看我在 C++11 中对 SeqLock 的尝试:
并且 GCC reordering up across load with `memory_order_seq_cst`. Is this allowed? 显示了另一个例子(这个例子导致了一个 gcc 错误)。
将这些从 C++11 std::atomic 移植到 C11 stdatomic 应该很简单。确保使用 atomic_store_explicit
,因为普通 atomic_store
的默认内存顺序是 memory_order_seq_cst
,速度较慢。
实际上,您无能为力地加快编写器使其商店在全球可见的速度。 CPU 内核已经尽快将存储从其存储缓冲区提交到其 L1d(遵守 x86 内存模型的限制,该模型不允许 StoreStore 重新排序)。
在 Xeon 上,请参阅
多核上的缓存是一致的,使用MESI保持一致性。
A reader 原子变量上的自旋等待可能是你能做的最好的,在自旋循环内使用 _mm_pause()
以避免在退出时清除内存顺序错误推测管道自旋环。
您也不希望在写入过程中醒来而不得不重试。您可能希望将 seq-lock 计数器放在与数据相同的缓存行中,因此这些存储有望合并到写入核心的存储缓冲区中。