我试图了解当 RPQ(读挂起队列)和 WPQ(写挂起队列)之间存在显着的队列压力差异时,内存控制器如何维护非临时加载和非临时存储之间的程序顺序。
考虑这个序列:
load A // goes to RPQ
ntstore A // goes to WPQ
如果 RPQ 有许多待处理条目(例如 20 个)并且 WPQ 相对空(例如 2 个条目),直观上看起来存储可以在加载完成之前到达 DRAM:
我编写了一个测试程序(见下文)来验证这个假设。其中:
核心测试顺序:
uint64_t val = MAGIC_A;
uint64_t addr = (uint64_t)&memory[test_idx].sentinel;
asm volatile(
"mov (%1), %%rax\n\t" // Load into rax
"movnti %%rbx, (%1)\n\t" // NT Store
"mov %%rax, %0\n\t" // Save loaded value
: "=r"(val)
: "r"(addr), "b"(MAGIC_B)
: "rax", "memory"
);
if (val == MAGIC_B) { // Would indicate store completed before load
local_violations++;
}
结果:使用 16 个线程和每个线程 10M 缓存行运行,我们看到 0 次违规。性能计数器确认我们达到了所需的队列压力:
$ sudo perf stat -e uncore_imc_0/unc_m_rpq_occupancy/ -e uncore_imc_0/unc_m_rpq_inserts/ -e uncore_imc_0/unc_m_wpq_occupancy/ -e uncore_imc_0/unc_m_wpq_inserts/ -e uncore_imc_3/unc_m_rpq_occupancy/ -e uncore_imc_3/unc_m_rpq_inserts/ -e uncore_imc_3/unc_m_wpq_occupancy/ -e uncore_imc_3/unc_m_wpq_inserts/ sleep 60
[sudo] password for vin:
Performance counter stats for 'system wide':
2,893,410,795,007 uncore_imc_0/unc_m_rpq_occupancy/
9,443,033,953 uncore_imc_0/unc_m_rpq_inserts/
574,954,888,344 uncore_imc_0/unc_m_wpq_occupancy/
32,101,285 uncore_imc_0/unc_m_wpq_inserts/
1,086,622,871 uncore_imc_3/unc_m_rpq_occupancy/
38,269,189 uncore_imc_3/unc_m_rpq_inserts/
76,056,378,805 uncore_imc_3/unc_m_wpq_occupancy/
31,895,245 uncore_imc_3/unc_m_wpq_inserts/
60.002128565 seconds time elapsed
在这种场景下,内存控制器如何维护程序顺序?鉴于队列深度的显着差异(RPQ ~306 与 WPQ ~18),什么机制阻止存储在其之前的加载之前完成?我怀疑除了简单的队列动态之外,一定还有某种排序机制,但我不明白它是什么。
这是完整的代码:https://gist.github.com/VinayBanakar/6841e553d274fa5b8a156c13937405c8
在 x86 上(与 ARM 和其他平台不同),我非常确定负载无法退出(变得非推测性并允许稍后的 insns 也退出),直到返回值。 这可以让 CPU 捕获内存顺序错误推测,因为硬件实际上会提前加载并检查 LoadLoad 顺序违规等内容,并在必要时破坏管道。 (
machine_clears.memory_ordering
性能事件)。
弱序 ISA 不需要这样做;一旦知道负载没有故障并且请求已发送,他们就可以让负载从 ROB(重新排序缓冲区)中退出。 因此它仅由加载缓冲区条目跟踪,而不是 ROB 条目。
存储无法从存储缓冲区提交到 LFB 或 L1d 缓存,直到它变得非推测性为止,否则可能会使错误推测的存储值对其他核心可见,而无法回滚。 (例如,在检测到较早的分支错误预测或错误指令后。)
因此,x86 乱序执行硬件从根本上无法进行 LoadStore 重新排序,即使是弱排序的 NT 存储也是如此。 您对内存控制器的非核心请求无法立即执行。
SSE4.1
movntdqa
来自 WC 内存的加载是弱排序的(与来自任何其他内存类型的 movntdqa
加载不同 - 与 NT 存储不同,指令不会覆盖排序语义)。 假设,它们可以被允许在数据到达之前(在响应非核心请求之前)退出。 这不会违反内存模型,因为它们可以通过更早或更晚的加载和存储自由地重新排序(我认为),并且可能需要一个完整的 mfence
来阻止它们的重新排序。 我不知道是否有真正的 CPU 让它们在数据仍在传输时退出,或者它们是否都像正常负载一样处理它们,除了不检查内存顺序错误推测。 我猜是后者,因为这是一个非常罕见的用例,而且好处可能很小。
但是您正在使用普通的
mov
加载普通全局变量,这些变量将位于 WB(回写)内存区域中,因此这些都不适用于您的测试用例。