Cur*_*ous 5 c performance x86 profiling prefetch
阅读何时应该使用预取?中接受的答案后 以及预取示例中的示例?,我在理解何时实际使用预取方面仍然存在很多问题。虽然这些答案提供了预取很有用的示例,但它们没有解释如何在实际程序中发现它。看起来像是随机猜测。
我特别对 intel x86 的 C 实现(prefetchnta、prefetcht2、prefetcht1、prefetcht0、prefetchw)感兴趣,这些实现可以通过 GCC 的__builtin_prefetch内在函数访问。我想知道:
perf。在这种情况下,什么指标(或它们之间的关系)表明有机会通过软件预取来提高性能?for (int i = 0; i < n; i++) {
// some code
double x = a[i];
// some code
}
Run Code Online (Sandbox Code Playgroud)
我应该在加载之前还是之后放置预取a[i]?它应该指向前方多远a[i+m]?我是否需要担心展开循环以确保我仅在缓存行边界上预取,或者它几乎是免费的,就像nop数据已经在缓存中一样?是否值得__builtin_prefetch连续使用多个调用来一次预取多个缓存行?
我如何才能看到软件预取对我的特定程序有帮助?
您可以检查缓存未命中的比例。perf借助硬件性能计数器,可以使用 VTune 来获取此信息。perf list例如,您可以获取该列表。该列表取决于目标处理器架构,但有一些通用事件。例如,L1-dcache-load-misses、LLC-load-misses和LLC-store-misses。除非您还获得了加载/存储的数量,否则获得缓存未命中的数量并不是很有用。有一些通用计数器,例如L1-dcache-loads,LLC-loads或LLC-stores。AFAIK,对于 L2,没有通用计数器(至少在英特尔处理器上),您需要使用特定的硬件计数器(例如l2_rqsts.miss在类似英特尔 Skylake 的处理器上)。要获取总体统计数据,您可以使用perf stat -e an_hardware_counter,another_one your_program. 可以在这里找到很好的文档。
当未命中的比例很大时,那么你应该尝试优化代码,但这只是一个提示。事实上,对于您的应用程序,您的应用程序的关键部分/时间可能会出现大量缓存命中,但也会出现许多缓存未命中。因此,缓存未命中可能会在所有其他缓存缺失中丢失。对于与 SIMD 相比具有大量标量代码的 L1 缓存引用来说尤其如此。一种解决方案是仅分析应用程序的特定部分,并利用它的知识来朝好的方向进行调查。性能计数器并不是真正自动搜索程序中问题的工具,而是帮助您验证/反驳某些假设或给出有关正在发生的情况的一些提示的工具。它为你提供解决神秘案件的证据,但所有工作都取决于你,侦探,来完成所有的工作。
如何找到缓存未命中最严重的负载?
一些硬件性能计数器是“精确的”,这意味着可以定位生成事件的指令。这非常有用,因为您可以判断哪些指令造成了最多的缓存未命中(尽管在实践中并不总是准确的)。您可以使用perf record+ perf reportso 来获取信息(有关更多信息,请参阅前面的教程)。
请注意,导致缓存未命中的原因有很多,并且只有少数情况可以通过使用软件预取来解决。
如何查看发生未命中的缓存级别来决定使用哪个预取(0,1,2)?
这在实践中通常很难选择,并且很大程度上取决于您的应用程序。理论上,该数字是一个提示,告诉处理器目标缓存行的局部性级别(例如,提取到 L1、L2 或 L3 缓存中)。例如,如果您知道数据应该很快被读取和重用,那么将其放在 L1 中是一个好主意。但是,如果使用 L1 并且您不希望只使用一次(或很少使用)的数据污染它,那么最好将数据提取到较低的缓存中。实际上,它有点复杂,因为一种架构的行为可能与另一种架构不同......请参阅什么是_mm_prefetch()局部性提示?了解更多信息。
这个问题就是一个用法示例。使用软件预取来避免某些特定步长的缓存垃圾问题。这是一种病态的情况,硬件预取器不是很有用。
假设我发现某个特定负载在特定缓存级别中遭受缺失,我应该在哪里放置预取?
这显然是最棘手的部分。您应该尽早预取高速缓存行,以便显着减少延迟,否则该指令是无用的,实际上可能是有害的。事实上,该指令在程序中占用了一些空间,需要进行解码,并使用可用于执行其他(更关键的)加载指令的加载端口。但是,如果为时已晚,则缓存行可能会被逐出并需要重新加载......
通常的解决方案是编写如下代码:
for (int i = 0; i < n; i++) {
// some code
const size_t magic_distance_guess = 200;
__builtin_prefetch(&data[i+magic_distance_guess]);
double x = a[i];
// some code
}
Run Code Online (Sandbox Code Playgroud)
通常根据基准设置的值在哪里magic_distance_guess(或者对目标平台的非常深入的了解,尽管实践经常表明即使是高技能的开发人员也无法找到最佳值)。
问题是延迟很大程度上取决于数据的来源和目标平台。在大多数情况下,开发人员无法真正知道何时进行预取,除非他们在唯一的给定目标平台上工作。这使得软件预取使用起来很棘手,并且当目标平台发生变化时通常是有害的(必须考虑代码的可维护性和指令的开销)。更不用说内置函数是依赖于编译器的,预取内在函数是依赖于体系结构的,并且没有标准的可移植方法来使用软件预取。
我是否需要担心展开循环以确保仅在缓存行边界上预取,或者如果数据已在缓存中,则它几乎像 nop 一样免费?
是的,预取指令不是免费的,因此最好每个缓存行仅使用 1 个指令(因为同一缓存行上的其他预取指令将毫无用处)。
是否值得连续使用多个 __builtin_prefetch 调用来一次预取多个缓存行?
这非常依赖于目标平台。现代主流 x86-64 处理器以无序方式并行执行指令,并且它们分析的指令窗口相当大。他们倾向于尽快执行负载以避免失误,并且他们通常非常适合此类工作。
在您的示例循环中,我希望硬件预取器应该做得很好,并且在(相对较新的)主流处理器上使用软件预取应该会更慢。
十年前,当硬件预取器还不是很智能时,软件预取非常有用,但现在它们往往非常好。此外,引导硬件预取器通常比使用软件预取指令更好,因为前者的开销较低。这就是为什么不鼓励软件预取(例如英特尔和大多数开发人员),除非您真的知道自己在做什么。