概括
考虑以下循环:
loop:
movl $0x1,(%rax)
add $0x40,%rax
cmp %rdx,%rax
jne loop
whererax
被初始化为大于 L3 缓存大小的缓冲区的地址。每次迭代都会对下一个缓存行执行存储操作。我希望从 L1D 发送到 L2 的 RFO 请求数或多或少等于访问的缓存行数。问题是,这似乎只是当我计算内核模式事件时的情况,即使程序在用户模式下运行,除了我在下面讨论的一种情况。分配缓冲区的方式似乎并不重要(.bss、.data 或来自堆)。
细节
我的实验结果如下表所示。所有实验都是在禁用超线程并启用所有硬件预取器的处理器上进行的。
我测试了以下三种情况:
- 没有初始化循环。也就是说,在上面显示的“主”循环之前不会访问缓冲区。我将这种情况称为
NoInit
. 在这种情况下只有一个循环。 - 首先使用每个高速缓存行的一条加载指令访问缓冲区。一旦所有的行都被触及,主循环就会被执行。我将这种情况称为
LoadInit
. 在这种情况下有两个循环。 - 首先使用每个高速缓存行的一条存储指令访问缓冲区。一旦所有的行都被触及,主循环就会被执行。我将这种情况称为
StoreInit
. 在这种情况下有两个循环。
下表显示了英特尔 CFL 处理器上的结果。这些实验是在 Linux 内核版本 4.4.0 上进行的。
下表显示了英特尔 HSW 处理器上的结果。请注意,HSW 中没有记录事件L2_RQSTS.PF_HIT
、L2_RQSTS.PF_MISS
和。OFFCORE_REQUESTS.ALL_REQUESTS
这些实验是在 Linux 内核版本 4.15 上进行的。
每个表的第一列包含性能监控事件的名称,其计数显示在其他列中。在列标签中,字母U
和K
分别代表用户模式和内核模式事件。对于有两个循环的情况,数字 1 和 2 分别用于指代初始化循环和主循环。例如,LoadInit-1K
表示LoadInit
案例初始化循环的内核模式计数。
表中显示的值由高速缓存行数标准化。它们也采用如下颜色编码。绿色越深,相对于同一表格中的所有其他单元格,该值就越大。但是,CFL 表的最后三行和 HSW 表的最后两行没有进行颜色编码,因为这些行中的某些值太大了。这些行被涂成深灰色,表示它们不像其他行那样用颜色编码。
我希望用户模式L2_RQSTS.ALL_RFO
事件的数量等于访问的高速缓存行的数量(即标准化值 1)。该事件在手册中描述如下:
计算对 L2 缓存的 RFO(读取所有权)请求的总数。L2 RFO 请求包括 L1D 需求 RFO 未命中以及 L1D RFO 预取。
它说这L2_RQSTS.ALL_RFO
不仅可以计算来自 L1D 的需求 RFO 请求,还可以计算 L1D RFO 预取。但是,我观察到事件计数不受两个处理器上是否启用或禁用 L1D 预取器的影响。但是即使 L1D 预取器可能会生成 RFO 预取,事件计数也应该至少与访问的缓存行数一样大。从两张表中可以看出,这仅是StoreInit-2U
. 相同的观察结果适用于表中显示的所有事件。
但是,事件的内核模式计数大约等于预期的用户模式计数。这与例如MEM_INST_RETIRED.ALL_STORES
(或MEM_UOPS_RETIRED.ALL_STORES
在 HSW 上)按预期工作相反。
由于 PMU 计数器寄存器的数量有限,我不得不将所有的实验分成四个部分。特别是,内核模式计数是由与用户模式计数不同的运行产生的。什么被计入同样的东西并不重要。我认为告诉你这一点很重要,因为这解释了为什么某些用户模式计数比相同事件的内核模式计数略大。
以深灰色显示的事件似乎多虑了。第 4 代和第 8 代 Intel 处理器规格手册确实提到了(分别是问题 HSD61 和 111)OFFCORE_REQUESTS_OUTSTANDING.DEMAND_RFO
可能会高估。但这些结果表明,它可能被高估了很多倍,而不仅仅是几个事件。
还有其他有趣的观察结果,但它们与问题无关,即:为什么 RFO 计数不符合预期?