【发布时间】:2021-06-10 14:21:13
【问题描述】:
所以我决定看看如何通过英特尔® 内在函数在 C 语言中使用 SSE、AVX 等。不是因为任何实际兴趣将其用于某事,而是出于纯粹的好奇心。尝试检查使用 AVX 的代码是否实际上比非 AVX 代码更快,结果让我有点惊讶。这是我的 C 代码:
#include <stdio.h>
#include <stdlib.h>
#include <emmintrin.h>
#include <immintrin.h>
/*** Sum up two vectors using AVX ***/
#define __vec_sum_4d_d64(src_vec1, src_vec2, dst_vec) \
_mm256_store_pd(dst_vec, _mm256_add_pd(_mm256_load_pd(src_vec1), _mm256_load_pd(src_vec2)));
/*** Sum up two vectors without AVX ***/
#define __vec_sum_4d(src_vec1, src_vec2, dst_vec) \
dst_vec[0] = src_vec1[0] + src_vec2[0];\
dst_vec[1] = src_vec1[1] + src_vec2[1];\
dst_vec[2] = src_vec1[2] + src_vec2[2];\
dst_vec[3] = src_vec1[3] + src_vec2[3];
int main (int argc, char *argv[]) {
unsigned long i;
double dvec1[4] = {atof(argv[1]), atof(argv[2]), atof(argv[3]), atof(argv[4])};
double dvec2[4] = {atof(argv[5]), atof(argv[6]), atof(argv[7]), atof(argv[8])};
#if 1
for (i = 0; i < 3000000000; i++) {
__vec_sum_4d(dvec1, dvec2, dvec2);
}
#endif
#if 0
for (i = 0; i < 3000000000; i++) {
__vec_sum_4d_d64(dvec1, dvec2, dvec2);
}
#endif
printf("%10.10lf %10.10lf %10.10lf %10.10lf\n", dvec2[0], dvec2[1], dvec2[2], dvec2[3]);
}
我只是将#if 1 切换到#if 0,反之则在“模式”(AVX 和非 AVX)之间切换。
我的期望是,使用 AVX 的循环至少会比另一个更快,但事实并非如此。我用gcc version 10.2.0 (GCC) 和这些:-O2 --std=gnu99 -lm -mavx2 标志编译了代码。
> time ./noavx.x86_64 1 2 3 4 5 6 7 8
3000000005.0000000000 6000000006.0000000000 9000000007.0000000000 12000000008.0000000000
real 0m2.150s
user 0m2.147s
sys 0m0.000s
> time ./withavx.x86_64 1 2 3 4 5 6 7 8
3000000005.0000000000 6000000006.0000000000 9000000007.0000000000 12000000008.0000000000
real 0m2.168s
user 0m2.165s
sys 0m0.000s
如您所见,它们的运行速度几乎相同。我还尝试将迭代次数增加十倍,但结果只会按比例放大。另请注意,两个可执行文件的打印输出值是相同的,所以我认为可以说两者执行相同的计算是可以的。深入挖掘,我看了看组装,更加困惑。以下是两者的重要部分(仅循环):
; With avx
1070: c5 fd 58 c1 vaddpd %ymm1,%ymm0,%ymm0
1074: 48 83 e8 01 sub $0x1,%rax
1078: 75 f6 jne 1070
; Without avx
1080: c5 fb 58 c4 vaddsd %xmm4,%xmm0,%xmm0
1084: c5 f3 58 cd vaddsd %xmm5,%xmm1,%xmm1
1088: c5 eb 58 d7 vaddsd %xmm7,%xmm2,%xmm2
108c: c5 e3 58 de vaddsd %xmm6,%xmm3,%xmm3
1090: 48 83 e8 01 sub $0x1,%rax
1094: 75 ea jne 1080
根据我的理解,第二个应该慢得多,因为除了递减计数器和条件跳转之外,其中的指令数量是它的四倍。为什么不慢? vaddsd 指令是否仅比 vaddpd 快四倍?
如果这是相关的,我的系统在支持 AVX 的 AMD Ryzen 5 2600X Six-Core Processor 上运行。
【问题讨论】:
-
这似乎是编译器可以在编译时预先计算的东西。关于 SO 的所有基准测试问题中约有 90% 是由错误的基准测试方法引起的。考虑将这两个双精度数组作为函数的参数,然后反汇编该函数。
-
除了其他cmets,注意可能会受到内存访问速度的限制。
-
ymm 寄存器比 xmm 寄存器宽。但是第二个循环使用更多的寄存器做更多的操作。我相信你的问题的答案是流水线。 CPU 很可能能够使用计算硬件在与一次 ymm 操作相同的时间内运行两次 xmm 操作。
-
@ZanLynx:不完全是:两者都受到 FP add 的 3 周期延迟作为循环携带依赖项的瓶颈,而不是吞吐量限制。一个
vaddpd ymm总体上比4xvaddpd/sd xmm便宜(2 微指令)(相同的后端端口为4 微指令,前端成本更高)。基本上,每个 YMM 操作与两个 XMM 操作消耗相同的吞吐量资源(以一些可能的前端差异为模),但前端和后端吞吐量都不是瓶颈。 -
@GimbaAghDurba:请注意,
gcc -O3启用自动矢量化,并希望为两个版本制作相同的 asm。或者可能是 2xvaddpd xmm,具体取决于其调整选项。
标签: c assembly x86 cpu-architecture avx