_builtin_prefetch() 中第二个参数的作用是什么?

ANT*_*ONY 4 c x86 assembly gcc prefetch

此处的 GCC 文档指定了 _buitin_prefetch 的用法。

第三个论点是完美的。若为0,编译器产生prefetchtnta(%rax)指令 若为1,编译器产生prefetcht2(%rax)指令 若为2,编译器产生prefetcht1(%rax)指令 若为3(默认),编译器产生prefetcht0 (%rax) 指令。

如果我们改变第三个参数,操作码已经相应地改变了。

但是第二个参数似乎没有任何效果。

__builtin_prefetch(&x,1,2);
__builtin_prefetch(&x,0,2);
__builtin_prefetch(&x,0,1);
__builtin_prefetch(&x,0,0);
Run Code Online (Sandbox Code Playgroud)

以上是生成的示例代码:

以下是组装:

 27:    0f 18 10                prefetcht1 (%rax)
  2a:   48 8d 45 fc             lea    -0x4(%rbp),%rax
  2e:   0f 18 10                prefetcht1 (%rax)
  31:   48 8d 45 fc             lea    -0x4(%rbp),%rax
  35:   0f 18 18                prefetcht2 (%rax)
  38:   48 8d 45 fc             lea    -0x4(%rbp),%rax
  3c:   0f 18 00                prefetchnta (%rax)
Run Code Online (Sandbox Code Playgroud)

可以观察到第三个参数的操作码的变化。但即使我更改了第二个参数(指定读或写),汇编代码也保持不变。<27,2a> 和 <2e,31>。所以它不会向机器提供任何信息。那么第二个论点的目的是什么?

Pet*_*des 5

正如玛格丽特指出的那样,参数之一是rw

Baseline x86-64 (SSE2) 不包括写预取指令,但它们作为 ISA 扩展存在。像往常一样,编译器不会使用它们,除非您告诉他们您正在为支持它的目标进行编译。(但它们将安全地作为 NOP 在任何非古代 CPU 上运行。)

这两条指令是:PREFETCHW(进入 L1d 缓存,如 PREFETCHT0)和PREFETCHWT1(进入 L2 缓存,如PREFETCHT1)。 他们通过发送一个 RFO(Read-For-Ownership)来预取一行进入 Exclusive MESI 状态。这会使每个其他内核中该行的每个其他副本无效。从该状态开始,存储缓冲区可以将数据提交到一行(并将其翻转为已修改),而无需任何进一步的核外流量。或者如果在驱逐前没有修改,可以简单地删除。

PREFETCHW 指令只是一个提示,不会影响程序行为。如果执行该指令,该指令会将数据移近处理器并使其他缓存副本无效,以预期将来要写入的行。

它们具有几乎相同的机器编码、相同的OF 0D操作码,仅在ModRM字段中/1/2在 ModRM/r字段中不同。就像读预取 PREFETCHT0/T1/T2/NTA 共享一个操作码并且仅通过ModRM字段中的/0(NTA)、/1(T0) 等进行区分一样/r。使用/r位作为额外的操作码位并不是唯一的;其他一操作数和立即指令也这样做。

相关:读或写预取之间的区别


PREFETCHW 最初出现在AMD 的 3DNow! ,但有自己的功能位,因此 CPU 可以表示支持它,但不支持其他 3DNow!(包装float在 MMX regs 中)说明。

PREFETCHWT1 也有自己的 CPUID 功能位,但可能与 AVX512PF 相关联。它似乎只在 Xeon Phi(Knight's Landing / Knight's Mill)中可用,而不是主流 Skylake-AVX512,与 AVX512PF 相同(https://en.wikipedia.org/wiki/AVX-512#CPUs_with_AVX-512)。(证据:根据英特尔的未来扩展手册,EAX=7/ECX=0 的 CPUID 在 ECX 中提供了一个功能位图,包括位 00:PREFETCHWT1(仅限英特尔® 至强融核™。) 还有邮件列表


__builtin_prefetch(p,1,2);使用 GCC 编译如下

  • 没有-m选项的PREFETCHT1 ,-march=haswell或较旧的 Intel。
  • 带有 AMD 目标的 PREFETCHW,例如-march=k8-march=bdver2(Piledriver)。
  • 带有-march=broadwell或更新的 Intel SnB 系列和/或-mprfchw任何架构的PREFETCHW 。
  • PREFETCHWT1 与-mprefetchwt1. (如果 PREFETCHW 也可用,gcc 将它用于 locality=3,但 PREFETCHWT1 用于 locality<=2。)出于某种原因,GCC 不会将其作为-march=knlor 的一部分启用-march=knm,但 clang 可以。我认为这是 GCC 的疏忽。

  • -mprefetchwt1暗示-mprfchw. 又见86选项在GCC手册中部分获得更多有关-march=native-march=whatever启用一组ISA扩展和一套-mtune=whatever得体。

Godbolt 编译器资源管理器上查看,对于-march=haswellvs. -march=broadwell -mprefetchwt1. 或者自己修改编译器参数。

clang -O3 -march=knl,并gcc -O3 -march=broadwell -mprefetchwt1制作相同的asm:

pref:
        prefetchwt1     [rdi]    #   __builtin_prefetch(p,1,2);  // KNL only, otherwise we get prefetchw
        prefetchw       [rdi]    #   __builtin_prefetch(p,1,3);

        prefetcht0      [rdi]    #   __builtin_prefetch(p,0,3);
        prefetcht1      [rdi]    #   __builtin_prefetch(p,0,2);
        prefetcht2      [rdi]    #   __builtin_prefetch(p,0,1);
        prefetchnta     [rdi]    #   __builtin_prefetch(p,0,0);
        ret
Run Code Online (Sandbox Code Playgroud)

另请注意,他们的0F 0D r/m8机器代码在没有 PREFETCHW 或 3DNow 的非古代 CPU 上解码为多字节 NOP!特征位。在早期的 64 位 Intel CPU 上,这是一条非法指令。(较新版本的 Windows 要求 PREFETCHW 无故障地执行,在这种情况下,人们谈论 CPU“支持 PREFETCHW”,即使它作为 NOP 运行)。

支持 PREFETCHW 但不支持 PREFETCHWT1 的 CPU 实际上可能会像 PREFETCHW 一样运行 PREFETCHWT1,但我还没有测试过。(它应该可以通过在不同内核上运行线程来测试,一个对某个位置进行重复存储,另一个执行 PREFETCHWT1 与 PREFETCHW 与读取预取与 NOP,并查看写入线程的吞吐量如何受到影响。)


不过,最好使用读意图预取而不是 NOP(就像 GCC 那样)。但是您可能不想执行 PREFETCHW 和 PREFETCHT0,因为太多的预取指令不是一件好事。(特别是对于 Intel IvyBridge,它在预取指令吞吐量方面存在某种性能错误。但 IvB 会将 PREFETCHW 作为 NOP 运行,因此您只能在该 uarch 上获得一个预取。)

调整软件预取很困难:如果硬件预取成功完成其工作,过多的预取意味着花费在实际工作上的执行资源更少。请参阅次优缓存行预取的成本每个程序员应该了解的关于内存的内容?

  • @ANTHONY:在 gcc 命令行上,显然是在编译时。https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html。我不认为它在链接时很重要,只是在从 `.c` 到 `.o`(或直接到可执行文件)时。它告诉 gcc 它允许使用哪些指令,并设置 `-mtune=`。 (2认同)

Mar*_*oom 4

来自您发布的同一链接:

有两个可选参数:rwlocalityrw的值是编译时常量 1 或 0;1 表示预取正在准备写入内存地址,默认值 0 表示预取正在准备读取。

x86 架构在读预取和写预取之间没有区别。
这并不意味着您应该忽略第二个参数,因为用 C 编写代码是为了提高可移植性。即使在您的机器中没有使用第二个参数,在编译到不同的体系结构时也可以使用它。

编辑 正如 @PeterCordes 在他的评论中指出的那样,x86 实际上有一个预取指令来预测写入。
它与其他预取指令不同,因为它使所获取的行的其他缓存实例无效(并将其设置为独占状态)。