考虑以下循环:
.loop:
add rsi, STRIDE
mov eax, dword [rsi]
dec ebp
jg .loop
其中STRIDE
是一些非负整数,并rsi
包含指向该bss
部分中定义的缓冲区的指针。这个循环是代码中唯一的循环。也就是说,它在循环之前没有被初始化或触摸。在 Linux 上,缓冲区的所有 4K 虚拟页面将按需映射到同一个物理页面。
我已经针对 0-8192 范围内的所有可能步幅运行了此代码。测量的次要页面错误数和主要页面错误数分别恰好是每个访问页面的 1 和 0。我还测量了Haswell 上的所有以下性能事件,以了解该范围内的所有进展。
DTLB_LOAD_MISSES.MISS_CAUSES_A_WALK:导致任何页面大小的页面遍历的所有 TLB 级别中的未命中。
DTLB_LOAD_MISSES.WALK_COMPLETED_4K:由于需求加载未命中导致任何 TLB 级别中的 4K 页面遍历而完成的页面遍历。
DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M:由于需求加载未命中导致任何 TLB 级别中的 2M/4M 页面遍历而完成的页面遍历。
DTLB_LOAD_MISSES.WALK_COMPLETED_1G:所有 TLB 级别的加载未命中会导致页面遍历完成。(1G)。
DTLB_LOAD_MISSES.WALK_COMPLETED:由于需求加载未命中,已完成的页面遍历任何页面大小的任何 TLB
对于所有步幅,大页面的两个计数器都为零。其他三个计数器很有趣,如下图所示。
对于大多数跨步,MISS_CAUSES_A_WALK
事件在每个访问的页面发生 5 次,WALK_COMPLETED_4K
andWALK_COMPLETED
事件在每个访问的页面发生 4 次。这意味着所有完成的页面遍历都是针对 4K 页面的。但是,有一个第五页没有完成。为什么每页有这么多的页面遍历?是什么导致这些页面走动?也许当一个page walk触发一个page fault时,在处理完这个fault之后,还会有另一个page walk,所以这可能算作两次完成的page walk。但是为什么有 4 个完成的页面遍历和一个明显取消的遍历?请注意,Haswell 上有一个单页漫游器(与 Broadwell 上的两个相比)。
我意识到有一个 TLB 预取器似乎只能预取下一页,如本线程中所述。根据该线程,预取器步行似乎没有被视为事件MISS_CAUSES_A_WALK
,WALK_COMPLETED_4K
我同意这一点。
这些似乎是这些高事件计数的两个原因:(1)页面错误导致指令被重新执行,这导致同一页面的第二个页面遍历,以及(2)在 TLB 中错过的多个并发访问. 否则,通过使用加载指令分配内存并在加载指令之后MAP_POPULATE
添加指令,每页发生一个事件和一个事件。没有,每页的计数会稍大一些。LFENCE
MISS_CAUSES_A_WALK
WALK_COMPLETED_4K
LFENCE
我尝试让每个负载访问无效的内存位置。在这种情况下,页面错误处理程序会引发一个 SIGSEGV 信号,我处理该信号以允许程序继续执行。使用该LFENCE
指令,我得到两个MISS_CAUSES_A_WALK
事件和每个 accessWALK_COMPLETED_4K
两个事件。没有,每次访问的计数会稍大一些。LFENCE
我还尝试过在循环中使用预取指令而不是按需加载。页面错误情况的结果与无效内存位置情况相同(这是有道理的,因为预取在两种情况下都失败):一个MISS_CAUSES_A_WALK
事件和一个WALK_COMPLETED_4K
事件每次预取。否则,如果预取是到具有有效内存转换的位置,则每页MISS_CAUSES_A_WALK
发生一个事件和一个WALK_COMPLETED_4K
事件。没有,每页的计数会稍大一些。LFENCE
所有实验都在同一个核心上运行。该内核上发生的 TLB 击落中断的数量几乎为零,因此它们对结果没有影响。我找不到一种简单的方法来衡量操作系统对内核的 TLB 驱逐次数,但我认为这不是一个相关因素。
尖刺
同样如上图所示,小步幅有一种特殊的模式。此外,在步幅 220 附近有一个非常奇怪的模式(尖峰)。我能够多次重现这些模式。下图放大了那个奇怪的图案,所以你可以清楚地看到它。我认为这种模式的原因是操作系统活动,而不是性能事件的工作方式或一些微架构效应,但我不确定。
循环展开的影响
@BeeOnRope 建议将其放入LFENCE
循环中并将其展开零次或多次,以更好地了解推测性、无序执行对事件计数的影响。下图显示了结果。当循环展开 0-63 次(单次迭代中的 1-64 个添加/加载指令对)时,每一行代表一个特定的加载步幅。y 轴按每页归一化。访问的页面数量与次要页面错误的数量相同。
我也运行了没有LFENCE
但有不同展开度的实验。我没有为这些制作图表,但我将在下面讨论主要区别。
我们可以得出以下结论:
- 当负载步幅小于 128 字节时,
MISS_CAUSES_A_WALK
在WALK_COMPLETED_4K
不同的展开度上表现出更高的变化。较大的步幅具有平滑的曲线,MISS_CAUSES_A_WALK
收敛到 3 或 5 并WALK_COMPLETED_4K
收敛到 3 或 4。 LFENCE
只有当展开度恰好为零时(即每次迭代有一个负载),似乎才会有所不同。如果没有LFENCE
,结果(如上所述)是每页5 个MISS_CAUSES_A_WALK
和 4 个事件。WALK_COMPLETED_4K
使用LFENCE
,它们都变为每页 3 个。对于较大的展开度,事件计数平均逐渐增加。当展开度至少为 1(即每次迭代至少有两个负载)时,LFENCE
基本上没有区别。这意味着上面的两个新图表对于没有负载的情况是相同的,LFENCE
除非每次迭代有一个负载。顺便说一句,只有在展开度为零且没有LFENCE
.- 一般来说,展开循环会减少触发和完成的行走次数,尤其是当展开程度较小时,无论负载步幅如何。无需展开,
LFENCE
可用于基本上获得相同的效果。展开后,无需使用LFENCE
. 无论如何,执行时间LFENCE
要长得多。因此,使用它来减少页面遍历将显着降低性能,而不是提高性能。