【发布时间】:2021-01-14 21:24:31
【问题描述】:
在英特尔内部指南中,vmulpd 和 vfmadd213pd 的延迟为 5,vaddpd 的延迟为 3。
我写了一些测试代码,但是所有的结果都慢了 1 个周期。
这是我的测试代码:
.CODE
test_latency PROC
vxorpd ymm0, ymm0, ymm0
vxorpd ymm1, ymm1, ymm1
loop_start:
vmulpd ymm0, ymm0, ymm1
vmulpd ymm0, ymm0, ymm1
vmulpd ymm0, ymm0, ymm1
vmulpd ymm0, ymm0, ymm1
sub rcx, 4
jg loop_start
ret
test_latency ENDP
END
#include <stdio.h>
#include <omp.h>
#include <stdint.h>
#include <windows.h>
extern "C" void test_latency(int64_t n);
int main()
{
SetThreadAffinityMask(GetCurrentThread(), 1); // Avoid context switch
int64_t n = (int64_t)3e9;
double start = omp_get_wtime();
test_latency(n);
double end = omp_get_wtime();
double time = end - start;
double freq = 3.3e9; // My CPU frequency
double latency = freq * time / n;
printf("latency = %f\n", latency);
}
我的 CPU 是 Core i5 4590,我将它的频率锁定在 3.3GHz。输出为:latency = 6.102484。
很奇怪,如果我把vmulpd ymm0, ymm0, ymm1改成vmulpd ymm0, ymm0, ymm0,那么输出就变成了:latency = 5.093745。
有解释吗?我的测试代码有问题吗?
更多结果
results on Core i5 4590 @3.3GHz
vmulpd ymm0, ymm0, ymm1 6.056094
vmulpd ymm0, ymm0, ymm0 5.054515
vaddpd ymm0, ymm0, ymm1 4.038062
vaddpd ymm0, ymm0, ymm0 3.029360
vfmadd213pd ymm0, ymm0, ymm1 6.052501
vfmadd213pd ymm0, ymm1, ymm0 6.053163
vfmadd213pd ymm0, ymm1, ymm1 6.055160
vfmadd213pd ymm0, ymm0, ymm0 5.041532
(without vzeroupper)
vmulpd xmm0, xmm0, xmm1 6.050404
vmulpd xmm0, xmm0, xmm0 5.042191
vaddpd xmm0, xmm0, xmm1 4.044518
vaddpd xmm0, xmm0, xmm0 3.024233
vfmadd213pd xmm0, xmm0, xmm1 6.047219
vfmadd213pd xmm0, xmm1, xmm0 6.046022
vfmadd213pd xmm0, xmm1, xmm1 6.052805
vfmadd213pd xmm0, xmm0, xmm0 5.046843
(with vzeroupper)
vmulpd xmm0, xmm0, xmm1 5.062350
vmulpd xmm0, xmm0, xmm0 5.039132
vaddpd xmm0, xmm0, xmm1 3.019815
vaddpd xmm0, xmm0, xmm0 3.026791
vfmadd213pd xmm0, xmm0, xmm1 5.043748
vfmadd213pd xmm0, xmm1, xmm0 5.051424
vfmadd213pd xmm0, xmm1, xmm1 5.049090
vfmadd213pd xmm0, xmm0, xmm0 5.051947
(without vzeroupper)
mulpd xmm0, xmm1 5.047671
mulpd xmm0, xmm0 5.042176
addpd xmm0, xmm1 3.019492
addpd xmm0, xmm0 3.028642
(with vzeroupper)
mulpd xmm0, xmm1 5.046220
mulpd xmm0, xmm0 5.057278
addpd xmm0, xmm1 3.025577
addpd xmm0, xmm0 3.031238
我的猜测
我把test_latency改成了这样:
.CODE
test_latency PROC
vxorpd ymm0, ymm0, ymm0
vxorpd ymm1, ymm1, ymm1
loop_start:
vaddpd ymm1, ymm1, ymm1 ; added this line
vmulpd ymm0, ymm0, ymm1
vmulpd ymm0, ymm0, ymm1
vmulpd ymm0, ymm0, ymm1
vmulpd ymm0, ymm0, ymm1
sub rcx, 4
jg loop_start
ret
test_latency ENDP
END
最后我得到了 5 个循环的结果。还有其他指令可以达到同样的效果:
vmovupd ymm1, ymm0
vmovupd ymm1, [mem]
vmovdqu ymm1, [mem]
vxorpd ymm1, ymm1, ymm1
vpxor ymm1, ymm1, ymm1
vmulpd ymm1, ymm1, ymm1
vshufpd ymm1, ymm1, ymm1, 0
但这些说明不能:
vmovupd ymm1, ymm2 ; suppose ymm2 is zeroed
vpaddq ymm1, ymm1, ymm1
vpmulld ymm1, ymm1, ymm1
vpand ymm1, ymm1, ymm1
在ymm指令的情况下,我猜避免1个额外循环的条件是:
- 所有输入都来自同一个域。
- 所有输入都足够新鲜。 (从旧值移动不起作用)
至于 VEX xmm,情况似乎有些模糊。好像和上半部状态有关,但不知道哪个更干净:
vxorpd ymm1, ymm1, ymm1
vxorpd xmm1, xmm1, xmm1
vzeroupper
对我来说是个难题。
【问题讨论】:
-
您进一步的测试都表明,如果您读取寄存器而不写入它,它的“额外延迟”属性可以保留整个循环,通过另一个操作数影响依赖链。 (而且
vzeroupper可以在 Haswell 上清除此属性。它不会在 Skylake 上。) -
@PeterCordes 其实
vzeroupper只能改变vmulpd xmm0, xmm0, xmm1的延迟;它对vmulpd ymm0, ymm0, ymm1没有任何改变。所以我还是很好奇。 -
有趣。在 Skylake 上,
vzeroupper也没有修复xmm,如果只读寄存器被污染,仍然很慢。但是Skylake uses a different SSE/AVX transition strategy than Haswell 所以很有可能vzeroupper有不同的实现细节,这也导致了这种不同。
标签: performance x86-64 intel cpu-architecture avx