读取或写入的预取之间的区别

gcc文档讨论了读取的预取和写入的预取之间的区别。 有什么技术差异?

在CPU级别上,软件预取(与硬件本身触发的预取相反)是向CPU提示即将访问线路的一种便捷方式,并且您希望预先预取它以节省延迟。

如果访问将是一个简单的读取,你会想要一个常规的预取,它的行为类似于来自内存的正常加载(除了没有阻塞CPU以防它错过,如果地址坏了没有错误,并且所有种类其他好处,取决于微观架构)。

但是,如果您打算写入该行,并且它也存在于另一个核心中,则简单的读取操作是不够的。 这是由于基于MESI的缓存处理协议。 核心在修改之前必须拥有一条线,这样它才能保持一致性(如果同一条线在多个核心中被修改,你将无法确保这些变化的正确排序,甚至可能会失去其中的一些,正常的WB内存类型不允许)。 相反,写入操作将从获取线路的所有权开始,并将其窥探出可能存在副本的任何其他核心/套接字。 只有这样才能发生写入。 读取操作(需求或预取)会使其他内核中的行处于共享状态,如果许多内核多次读取该行,这很好,但如果您的内核稍后写入它则无法帮助您。

为了允许对稍后写入的行进行有用的预取,大多数CPU公司都支持用于写入的特殊预取。 在x86中,Intel和AMD都支持prefetchW指令,该指令应该具有写入的效果(即 – 获取线路的唯一所有权,并且如果有则使其他任何副本无效)。 请注意,并非所有CPU都支持(即使在同一系列中,并非所有代都支持它),并非所有编译器版本都支持它。

这是一个例子(使用gcc 4.8.2) – 请注意,您需要在此处明确启用它 –

#include  int main() { long long int a[100]; __builtin_prefetch (&a[0], 0, 0); __builtin_prefetch (&a[16], 0, 1); __builtin_prefetch (&a[32], 0, 2); __builtin_prefetch (&a[48], 0, 3); __builtin_prefetch (&a[64], 1, 0); return 0; } 

使用gcc -O3 -mprfchw prefetchw.c -c编译,:

 0000000000000000 
: 0: 48 81 ec b0 02 00 00 sub $0x2b0,%rsp 7: 48 8d 44 24 88 lea -0x78(%rsp),%rax c: 0f 18 00 prefetchnta (%rax) f: 0f 18 98 80 00 00 00 prefetcht2 0x80(%rax) 16: 0f 18 90 00 01 00 00 prefetcht1 0x100(%rax) 1d: 0f 18 88 80 01 00 00 prefetcht0 0x180(%rax) 24: 0f 0d 88 00 02 00 00 prefetchw 0x200(%rax) 2b: 31 c0 xor %eax,%eax 2d: 48 81 c4 b0 02 00 00 add $0x2b0,%rsp 34: c3 retq

如果您使用第二个参数,您会注意到prefetchW的提示级别被忽略,因为它不支持时间级别提示。 顺便说一下,如果删除-mprfchw标志,gcc会将其转换为正常的读取预取(我没有尝试过不同的-march / mattr设置,也许其中一些也包括它)。

区别在于您是否希望只能很快读取内存,或者还要编写内存。 在后一种情况下,CPU可能能够以不同方式进行优化。 请记住,预取只是一个提示,因此GCC可能会忽略它。

引用GCC预取项目页面 :

一些数据预取指令区分了预期要读取的存储器和预期要写入的存储器。 当要写入数据时,预取指令可以将块移动到高速缓存中,以便预期的存储将进入高速缓存。 用于写入的预取通常将数据以独占或修改状态带入高速缓存。