什么将用于线程之间的数据交换,这些线程在具有 HT 的一个核心上执行?

What will be used for data exchange between threads are executing on one Core with HT?

Hyper-Threading Technology is a form of simultaneous multithreading technology introduced by Intel.

These resources include the execution engine, caches, and system bus interface; the sharing of resources allows two logical processors to work with each other more efficiently, and allows a stalled logical processor to borrow resources from the other one.

在具有超线程的英特尔 CPU 中,一个 CPU-Core(具有多个 ALU)可以在同一时钟上执行来自 2 个线程的指令。两个线程共享:存储缓冲区、缓存 L1/L2 和系统总线。

但是如果两个线程在一个核心上同时执行,线程 1 存储原子值,线程 2 加载这个值,将用于此交换的内容:共享存储缓冲区、共享缓存 L1 / L2 或照常缓存 L3?

如果 2 个线程来自同一个进程(相同的虚拟地址 space)和来自两个不同的进程(不同的虚拟地址 space),会发生什么情况?

Sandy Bridge Intel CPU - 缓存 L1:

我想你可以往返 L1。 (与单线程内的 store->load forwarding 不同,后者甚至更快。)

,它告诉我们很多关于这将如何工作的信息。我还没有测试其中的大部分内容,所以如果我的预测与实验不符,请告诉我。

更新:有关吞吐量和延迟的一些实验性测试,请参阅


存储必须在写入线程中退出,然后在一段时间后从 store buffer/queue 提交到 L1。那时它对另一个线程可见,并且从任一线程对该地址的加载应该在 L1 中命中。在此之前,另一个线程应该使用旧数据进行 L1 命中,而存储线程应该通过存储->加载转发来获取存储的数据。

当存储 uop 执行时,存储数据进入存储缓冲区,但它不能提交到 L1,直到它被认为是非推测的,即它退休。但是存储缓冲区也将 ROB(乱序核心中的 ReOrder 缓冲区)的退役与对 L1 的承诺分离开来,这对于缓存中未命中的存储非常有用。无序核心可以继续工作,直到存储缓冲区填满。


如果两个线程 运行 使用超线程在同一个内核上运行,如果它们不使用内存栅栏,则可以看到 StoreLoad 重新排序,因为线程之间不会发生存储转发。 Jeff Preshing's Memory Reordering Caught in the Act 代码可用于在实践中对其进行测试,使用 CPU 与同一物理核心的不同逻辑 CPU 线程上的 运行 亲和力。

必须使其存储全局可见(提交到 L1)作为其执行的一部分,否则它不会是原子的。只要数据不跨越缓存行之间的边界,它就可以锁定该缓存行。 (据我所知,这就是 CPUs 通常如何实现原子 RMW 操作,如 lock add [mem], 1lock cmpxchg [mem], rax。)

无论哪种方式,一旦完成,核心的 L1 缓存中的数据就会很热,其中任何一个线程都可以通过加载它来获得缓存命中。

我怀疑两个超线程对共享计数器进行原子增量(或任何其他 locked 操作,如 xchg [mem], eax)将实现与单个线程大致相同的吞吐量。这比两个线程 运行 在单独的物理内核上 高,其中缓存行必须在两个内核的 L1 缓存之间反弹(通过 L3)。

movNT (Non-Temporal) weakly-ordered stores绕过缓存,并将它们的数据放入行填充缓冲区。如果一开始它在缓存中很热,它们也会从 L1 中逐出该行。它们可能必须在数据进入填充缓冲区之前退出,因此来自其他线程的加载可能根本看不到它,直到它进入填充缓冲区。然后它可能与 movnt 存储相同,然后在单个线程内加载。 (即往返 DRAM,几百个延迟周期)。不要将 NT 存储用于您希望另一个线程立即读取的一小段数据。


L1 命中是可能的,因为 Intel CPUs 共享 L1 缓存的方式。 Intel 使用 virtually indexed, physically tagged (VIPT) L1 caches in most (all?) of their designs. (e.g. the Sandybridge family.) But since the index bits (which select a set of 8 tags) are below the page-offset, it behaves exactly like a PIPT cache (think of it as translation of the low 12 bits being a no-op), but with the speed advantage of a VIPT cache: it can fetch the tags from a set in parallel with the TLB lookup to translate the upper bits. See the "L1 also uses speed tricks that wouldn't work if it was larger" paragraph in this answer

由于 L1d 缓存的行为类似于 PIPT,并且相同的物理地址实际上意味着相同的内存,因此无论是同一进程的 2 个线程具有相同的缓存行虚拟地址,还是两个线程都无关紧要单独的进程将共享内存块映射到每个进程中的不同地址。这就是为什么 L1d 可以(并且现在)被两个超线程竞争而没有误报缓存命中的风险。与 dTLB 不同,dTLB 需要用核心 ID 标记其条目。

此答案的先前版本在此处有一段基于 Skylake 降低了 L1 关联性的错误想法。 Skylake 的 L2 是 4 路,而 Broadwell 和更早版本是 8 路。尽管如此, 可能还是令人感兴趣的。


英特尔的 x86 manual vol3, chapter 11.5.6 文档表明 Netburst (P4) 可以选择 以这种方式工作。默认值为 "Adaptive mode",它允许核心内的逻辑处理器共享数据。

有一个"shared mode":

In shared mode, the L1 data cache is competitively shared between logical processors. This is true even if the logical processors use identical CR3 registers and paging modes.

In shared mode, linear addresses in the L1 data cache can be aliased, meaning that one linear address in the cache can point to different physical locations. The mechanism for resolving aliasing can lead to thrashing. For this reason, IA32_MISC_ENABLE[bit 24] = 0 is the preferred configuration for processors based on the Intel NetBurst microarchitecture that support Intel Hyper-Threading Technology

对于 Nehalem / SnB uarches 中的超线程,它没有说明任何内容,所以我假设他们在另一个 uarch 中引入 HT 支持时没有包括 "slow mode" 支持,因为他们知道他们会"fast mode" 在 netburst 中正常工作。我有点想知道这个模式位是否只存在于万一他们发现了一个错误并且不得不通过微代码更新来禁用它。

此答案的其余部分仅针对 P4 的正常设置,我很确定这也是 Nehalem 和 SnB 家族 CPU 的工作方式。


理论上有可能构建一个 OOO SMT CPU 核心,使一个线程的存储在它们退出后立即对另一个线程可见,但在它们离开存储缓冲区之前并承诺 L1d(即在它们变得全局可见之前)。这不是英特尔设计的工作方式,因为它们静态地分区存储队列而不是竞争性地共享它。

即使线程共享一个存储缓冲区,也不允许在线程之间为尚未退休的存储转发存储,因为它们在那时仍然是推测性的。这会将两个线程绑定在一起以防止分支预测错误和其他回滚。

为多个硬件线程使用共享存储队列将需要额外的逻辑来始终转发到来自同一线程的加载,但仅将退休的存储转发到来自其他线程的加载。除了晶体管数量之外,这可能会产生显着的电力成本。您不能完全忽略非退休商店的商店转发,因为那样会破坏单线程代码。

一些 POWER CPU 可能真的会这样做;对于并非所有线程都同意商店的单一全局订单,这似乎是最可能的解释。 .

, this wouldn't work for an x86 CPU, only for an ISA that doesn't guarantee a Total Store Order,因为这会让 SMT 兄弟姐妹在它变得全局对其他核心可见之前看到你的商店。

TSO 可以通过将同级存储缓冲区中的数据视为推测性数据来保留,或者在任何缓存未命中加载之前都不会发生(因为 L1D 缓存中保持热的行不能包含来自其他存储的新存储核心)。 IDK,我还没有完全考虑到这一点。它似乎过于复杂,并且可能无法在保持 TSO 的同时进行有用的转发,甚至超出了拥有共享存储缓冲区或探测同级存储缓冲区的复杂性。