我认为@BeeOnRope 的回答完全回答了我的问题。我想根据@BeeOnRope 的回答和下面的评论在这里添加一些额外的细节。特别是,我将展示如何确定性能事件是否针对所有负载步幅在每次迭代中发生固定次数。
通过查看代码很容易看出执行一次迭代需要 3 微秒。最初的几次加载可能会在 L1 缓存中丢失,但随后所有的加载都将在缓存中命中,因为所有虚拟页面都映射到同一个物理页面,并且英特尔处理器中的 L1 被物理标记和索引。所以3微秒。现在考虑UOPS_RETIRED.ALL
性能事件,它在 uop 退出时发生。我们希望看到3 * number of iterations
这样的事件。执行期间发生的硬件中断和页面错误需要微码辅助来处理,这可能会扰乱性能事件。因此,对于性能事件 X 的特定度量,每个计数事件的来源可以是:
- 正在分析的代码的指令。我们称之为 X 1。
- Uops 用于引发由于正在分析的代码尝试访问内存而发生的页面错误。我们称之为 X 2。
- 由于异步硬件中断或引发软件异常,Uops 用于调用中断处理程序。我们称之为 X 3。
因此,X = X 1 +X 2 + X 3。
由于代码很简单,我们能够通过静态分析确定 X 1 = 3。但我们对 X 2和 X 3一无所知,这可能不是每次迭代的常数。UOPS_RETIRED.ALL
我们可以使用来测量 X。幸运的是,对于我们的代码,页面错误的数量遵循一个常规模式:每页访问恰好一个(可以使用perf
)。可以合理地假设引发每个页面错误都需要相同数量的工作,因此每次都会对 X 产生相同的影响。请注意,这与每次迭代的页面错误数相反,对于不同的加载步幅,这是不同的。作为执行每页访问的循环的直接结果而引退的微指令数是恒定的。我们的代码不会引发任何软件异常,因此我们不必担心它们。硬件中断怎么办?好吧,在 Linux 上,只要我们在未分配处理鼠标/键盘中断的内核上运行代码,唯一真正重要的中断就是本地 APIC 计时器。幸运的是,这种中断也经常发生。只要每页花费的时间量相同,定时器中断对 X 的影响将是每页恒定的。
我们可以将前面的等式简化为:
X = X 1 + X 4。
因此,对于所有负载步幅,
(每页 X)-(每页 X 1)=(每页 X 4)= 常数。
现在我将讨论为什么这是有用的,并提供使用不同性能事件的示例。我们将需要以下符号:
ec = total number of performance events (measured)
np = total number of virtual memory mappings used = minor page faults + major page faults (measured)
exp = expected number of performance events per iteration *on average* (unknown)
iter = total number of iterations. (statically known)
请注意,一般来说,我们不知道或不确定我们感兴趣的性能事件,这就是我们需要测量它的原因。退休的微商的案例很容易。但总的来说,这是我们需要通过实验来发现或验证的。本质上,exp
是性能事件的计数,ec
但不包括引发页面错误和中断的事件。
基于上述论点和假设,我们可以推导出以下等式:
C = (ec/np) - (exp*iter/np) = (ec - exp*iter)/np
这里有两个未知数:常数C
和我们感兴趣的值exp
。所以我们需要两个方程来计算未知数。由于该等式适用于所有步幅,因此我们可以对两个不同的步幅使用测量值:
C = (ec 1 - exp*iter)/np 1
C = (ec 2 - exp*iter)/np 2
我们可以找到exp
:
(ec 1 - exp*iter)/np 1 = (ec 2 - exp*iter)/np 2
ec 1 *np 2 - exp*iter*np 2 = ec 2 *np 1 - exp*iter*np 1
ec 1 *np 2 - ec 2 *np 1 = exp*iter*np 2 - exp*iter*np 1
ec 1 *np 2 - ec 2 *np 1 = exp*iter*(np 2 - np 1 )
因此,
exp = (ec 1 *np 2 - ec 2 *np 1 )/(iter*(np 2 - np 1 ))
让我们将此等式应用于UOPS_RETIRED.ALL
。
步幅1 = 32
iter = 1000 万
np 1 = 1000 万 * 32 / 4096 = 78125
ec 1 = 51410801
步幅2 = 64
iter = 1000 万
np 2 = 1000 万 * 64 / 4096 = 156250
ec 2 = 72883662
exp = (51410801*156250 - 72883662*78125)/(10m*(156250 - 78125))
= 2.99
好的!非常接近每次迭代预期的 3 个退役微指令。
C = (51410801 - 2.99*10m)/78125 = 275.3
我计算C
了所有的步幅。它不完全是一个常数,但所有步幅都是 275+-1。
exp
对于其他性能事件可以类似地得出:
MEM_LOAD_UOPS_RETIRED.L1_MISS
: exp
= 0
MEM_LOAD_UOPS_RETIRED.L1_HIT
: exp
= 1
MEM_UOPS_RETIRED.ALL_LOADS
: exp
= 1
UOPS_RETIRED.RETIRE_SLOTS
: exp
= 3
那么这适用于所有表演活动吗?好吧,让我们尝试一些不太明显的东西。例如RESOURCE_STALLS.ANY
,考虑出于任何原因测量分配器停顿周期。exp
仅通过查看代码很难判断应该多少。请注意,对于我们的代码,RESOURCE_STALLS.ROB
和RESOURCE_STALLS.RS
是零。只有RESOURCE_STALLS.ANY
在这里很重要。借助不同步幅的方程exp
和实验结果,我们可以计算exp
.
步幅1 = 32
iter = 1000 万
np 1 = 1000 万 * 32 / 4096 = 78125
ec 1 = 9207261
步幅2 = 64
iter = 1000 万
np 2 = 1000 万 * 64 / 4096 = 156250
ec 2 = 16111308
exp = (9207261*156250 - 16111308*78125)/(10m*(156250 - 78125))
= 0.23
C = (9207261 - 0.23*10m)/78125 = 88.4
我计算C
了所有的步幅。嗯,它看起来并不固定。也许我们应该使用不同的步幅?尝试没有坏处。
步幅1 = 32
iter 1 = 1000 万
np 1 = 1000 万 * 32 / 4096 = 78125
ec 1 = 9207261
步幅2 = 4096
iter 2 = 100 万
np 2 = 100 万 * 4096 / 4096 = 1m
ec 2 = 102563371
exp = (9207261*1m - 102563371*78125)/(1m*1m - 10m*78125))
= 0.01
C = (9207261 - 0.23*10m)/78125 = 88.4
(请注意,这次我使用了不同数量的迭代只是为了表明您可以做到这一点。)
我们得到了不同的值exp
。我已经计算C
了所有的步幅,但它看起来仍然不是恒定的,如下图所示。对于较小的步幅,它会显着变化,然后在 2048 年之后会略有变化。这意味着每页存在固定数量的分配器停顿周期的一个或多个假设并不那么有效。换句话说,不同步幅的分配器停顿周期的标准偏差是显着的。
![在此处输入图像描述](https://i.stack.imgur.com/EX2BO.png)
对于UOPS_RETIRED.STALL_CYCLES
性能事件,exp
= -0.32,标准偏差也很显着。这意味着每页存在固定数量的退休停顿周期的一个或多个假设并不那么有效。
![在此处输入图像描述](https://i.stack.imgur.com/vLHQK.png)
我开发了一种简单的方法来更正已测量的退役指令数量。每个触发的页面错误都会将一个额外的事件添加到已停用的指令计数器中。例如,假设在某个固定的迭代次数(例如 2 次)之后定期发生页面错误。也就是说,每两次迭代,就会触发一次错误。当步幅为 2048 时,问题中的代码会发生这种情况。由于我们预计每次迭代有 4 条指令退出,因此在发生页面错误之前预期退出指令的总数为 4*2 = 8。由于页面错误增加了一个退休指令计数器的额外事件,两次迭代将被测量为 9 而不是 8。也就是说,每次迭代 4.5。当我实际测量 2048 步长案例的退役指令数时,它非常接近 4.5。在所有情况下,当我应用此方法静态预测每次迭代测量的退役指令的值时,误差始终小于 1%。尽管存在硬件中断,但这是非常准确的。我认为只要总执行时间少于 50 亿个核心周期,硬件中断不会对退役指令计数器产生任何重大影响。(我的每个实验都不超过 50 亿次循环,这就是原因。)但如上所述,必须始终注意发生的故障数量。
正如我上面所讨论的,有许多性能计数器可以通过计算每页值来纠正。另一方面,可以通过考虑迭代次数来纠正引退指令计数器以获得页面错误。RESOURCE_STALLS.ANY
也许UOPS_RETIRED.STALL_CYCLES
可以像退休的指令计数器一样进行更正,但我没有调查这两个。