我看到一个简单的存储循环出乎意料地表现不佳,这个存储循环有两个存储:一个具有16字节的正向步长,另一个总是位于同一位置1,如下所示:
volatile uint32_t value;
void weirdo_cpp(size_t iters, uint32_t* output) {
uint32_t x = value;
uint32_t *rdx = output;
volatile uint32_t *rsi = output;
do {
*rdx = x;
*rsi = x;
rdx += 4; // 16 byte stride
} while (--iters > 0);
}
Run Code Online (Sandbox Code Playgroud)
在汇编这个循环可能3看起来像:
weirdo_cpp:
...
align 16
.top:
mov [rdx], eax ; stride 16
mov [rsi], eax ; never changes
add rdx, 16
dec rdi
jne .top
ret
Run Code Online (Sandbox Code Playgroud)
当访问的存储区域在L2中时,我希望每次迭代运行少于3个周期.第二个商店只是一直在同一个位置,应该添加一个周期.第一个商店意味着从L2引入一条线,因此每4次迭代也会驱逐一条线.我不确定你如何评估L2成本,但即使你保守估计L1只能在每个周期中执行以下操作之一:(a)提交商店或(b)从L2接收一行或(c)将一条线驱逐到L2,对于stride-16商店流,你会得到1 + 0.25 + …
假设我们有这个伪代码,但它ptr不在任何 CPU 缓存中:
prefetch_to_L1 ptr
/* 20 cycles */
load ptr
Run Code Online (Sandbox Code Playgroud)
由于ptr在主存中,预取操作的延迟(从预取指令解码到ptr在L1高速缓存中可用)远大于20个周期。正在进行的预取是否会减少负载的延迟?或者预取除非在加载之前完成,否则就没用吗?
天真地(对内存系统如何工作没有太多了解)我可以看到它以两种方式工作:
其中之一是正确的吗?还有我没有想到的第三种选择吗?我对 Skylake 特别感兴趣,但也只是想建立一些一般的直觉。
optimization performance intel cpu-architecture micro-architecture
此处的 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>。所以它不会向机器提供任何信息。那么第二个论点的目的是什么?