读取或写入预取之间的区别
Difference between prefetch for read or write
gcc docs 讨论了读预取和写预取之间的区别。技术差异是什么?
不同之处在于您是否希望该内存很快被读取,或者也被写入。在后一种情况下,CPU 可能能够进行不同的优化。请记住,预取只是一个提示,因此 GCC 可能会忽略它。
一些数据预取指令区分了预期被读取的内存和预期被写入的内存。当要写入数据时,预取指令可以将块移动到缓存中,以便将预期的存储到缓存中。写预取通常将数据以独占或修改状态带入缓存。
在 CPU 级别,软件预取(与由硬件本身触发的预取相反)是向 CPU 提示某行即将被访问的便捷方式,并且您希望提前预取它以节省延迟。
如果访问是简单的读取,您需要定期预取,其行为类似于从内存中正常加载(除了不阻塞 CPU 以防它丢失,而不是错误如果地址不好,还有其他各种好处,具体取决于微架构)。
但是,如果您打算写入该行,并且它也存在于另一个核心中,那么简单的读取操作是不够的。这是由于基于 MESI 的缓存处理协议。核心必须在修改某行之前拥有该行的所有权,以保持一致性(如果同一行在多个核心中被修改,您将无法确保这些更改的正确顺序,甚至可能会丢失其中一些,这在普通 WB 内存类型上是不允许的)。
相反,写入操作将从获取线路的所有权开始,并从可能持有副本的任何其他核心/套接字中窥探它。只有这样才能进行写入。
读取操作(需求或预取)会使其他内核中的行处于共享状态,如果该行被许多内核多次读取,这很好,但如果您的内核稍后写入它,则无济于事。
为了允许对稍后写入的行进行有用的预取,大多数 CPU 公司支持特殊的写入预取。在 x86 中,Intel 和 AMD 都支持 prefetchW 指令,它应该具有写入的效果(即 - 获得一行的唯一所有权,并使任何其他副本无效)。请注意,并非所有 CPU 都支持它(即使在同一个家族中,也并非所有世代都支持它),并且并非所有编译器版本都支持它。
这是一个示例(使用 gcc 4.8.2)- 请注意,您需要在此处明确启用它 -
#include <emmintrin.h>
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 <main>:
0: 48 81 ec b0 02 00 00 sub [=11=]x2b0,%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 [=11=]x2b0,%rsp
34: c3 retq
如果您使用第二个参数,您会注意到 prefetchW 会忽略提示级别,因为它不支持时间级别提示。顺便说一句,如果你删除 -mprfchw 标志,gcc 会将其转换为正常的读取预取(我没有尝试过不同的 -march/mattr 设置,也许其中一些也包括它)。
gcc docs 讨论了读预取和写预取之间的区别。技术差异是什么?
不同之处在于您是否希望该内存很快被读取,或者也被写入。在后一种情况下,CPU 可能能够进行不同的优化。请记住,预取只是一个提示,因此 GCC 可能会忽略它。
一些数据预取指令区分了预期被读取的内存和预期被写入的内存。当要写入数据时,预取指令可以将块移动到缓存中,以便将预期的存储到缓存中。写预取通常将数据以独占或修改状态带入缓存。
在 CPU 级别,软件预取(与由硬件本身触发的预取相反)是向 CPU 提示某行即将被访问的便捷方式,并且您希望提前预取它以节省延迟。
如果访问是简单的读取,您需要定期预取,其行为类似于从内存中正常加载(除了不阻塞 CPU 以防它丢失,而不是错误如果地址不好,还有其他各种好处,具体取决于微架构)。
但是,如果您打算写入该行,并且它也存在于另一个核心中,那么简单的读取操作是不够的。这是由于基于 MESI 的缓存处理协议。核心必须在修改某行之前拥有该行的所有权,以保持一致性(如果同一行在多个核心中被修改,您将无法确保这些更改的正确顺序,甚至可能会丢失其中一些,这在普通 WB 内存类型上是不允许的)。 相反,写入操作将从获取线路的所有权开始,并从可能持有副本的任何其他核心/套接字中窥探它。只有这样才能进行写入。 读取操作(需求或预取)会使其他内核中的行处于共享状态,如果该行被许多内核多次读取,这很好,但如果您的内核稍后写入它,则无济于事。
为了允许对稍后写入的行进行有用的预取,大多数 CPU 公司支持特殊的写入预取。在 x86 中,Intel 和 AMD 都支持 prefetchW 指令,它应该具有写入的效果(即 - 获得一行的唯一所有权,并使任何其他副本无效)。请注意,并非所有 CPU 都支持它(即使在同一个家族中,也并非所有世代都支持它),并且并非所有编译器版本都支持它。
这是一个示例(使用 gcc 4.8.2)- 请注意,您需要在此处明确启用它 -
#include <emmintrin.h>
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 <main>:
0: 48 81 ec b0 02 00 00 sub [=11=]x2b0,%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 [=11=]x2b0,%rsp
34: c3 retq
如果您使用第二个参数,您会注意到 prefetchW 会忽略提示级别,因为它不支持时间级别提示。顺便说一句,如果你删除 -mprfchw 标志,gcc 会将其转换为正常的读取预取(我没有尝试过不同的 -march/mattr 设置,也许其中一些也包括它)。