微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 AVX 矢量内在函数手动矢量化的运行速度与 Ryzen 上添加的 4 个标量 FP 的速度大致相同? 使用 AVX没有 AVX关于基准测试

如何解决使用 AVX 矢量内在函数手动矢量化的运行速度与 Ryzen 上添加的 4 个标量 FP 的速度大致相同? 使用 AVX没有 AVX关于基准测试

所以我决定看看如何通过英特尔® Intrinsics 在 C 中使用 SSE、AVX 等。不是因为有任何实际兴趣将它用于某事,而是出于纯粹的好奇心。试图检查使用 AVX 的代码是否实际上比非 AVX 代码快,结果让我有点惊讶。这是我的 C 代码

>>> out

      x    y      value
0    20    1   8.046723
1    20    2   4.613252
2    20    3  14.546438
3    20    4   0.000000
4    20    5   0.000000
5    20    6   0.000000
6    20    7   0.000000
7    20    8   2.285226
8    20    9   0.000000
...  ... ...   ...
794  29  395   0.000000
795  29  396   0.000000
796  29  397   0.000000
797  29  398   0.000000
798  29  399   0.000000
799  29  400   0.000000

我只是将 #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,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); } #endif printf("%10.10lf %10.10lf %10.10lf %10.10lf\n",dvec2[0],dvec2[1],dvec2[2],dvec2[3]); } 切换到 #if 1,反之亦然,以在“模式”(AVX 和非 AVX)之间切换。 我的期望是,使用 AVX 的循环至少会比另一个快一些,但事实并非如此。我使用 #if 0 和这些:gcc version 10.2.0 (GCC) 标志编译了代码

-O2 --std=gnu99 -lm -mavx2

如您所见,它们的运行速度几乎相同。我还尝试将迭代次数增加 10 倍,但结果只会按比增加。另请注意,两个可执行文件的打印输出值相同,因此我认为最好说两者都执行相同的计算。深入挖掘,我查看了程序集,更加困惑。以下是两者的重要部分(仅循环):

> 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 快四倍吗?

如果这是相关的,我的系统在支持 AVX 的 vaddpd 上运行。

解决方法

使用 AVX

; With avx
1070:   c5 fd 58 c1             vaddpd %ymm1,%ymm0,%ymm0
1074:   48 83 e8 01             sub    $0x1,%rax
1078:   75 f6                   jne    1070

这个循环使用 ymm0 作为累加器。换句话说,它正在执行 ymm0 += ymm1 (这是一个向量操作;一次添加 4 个双精度值)。因此它对 ymm0 具有循环携带依赖性(每个新添加都必须等待前一个添加完成并使用结果开始下一个添加)。 vaddpd 的延迟 = 3,Zen+ 的吞吐量 = 1(根据 https://www.uops.info/table.html)。循环携带的依赖性使此循环在 vaddpd延迟 上成为瓶颈,因此您的循环最多可以获得 3 个循环/迭代。 CPU 中只有一个 vaddpd 附加项在运行中,这大大地利用了它的功能。

为了更快地添加更多的累加器(有更多的向量要求和)。它可以(理论上)由于流水线(3 个完整的 ymm 次添加在进行中)而快 3 倍,只要它不受其他东西的限制。

没有 AVX

; 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

此循环将结果累加到 4 个不同的累加器中。基本上它是这样做的:

xmm0 += xmm4
xmm1 += xmm5
xmm2 += xmm7
xmm3 += xmm6

所有这些加法都是相互独立的(而且它们是标量加法,因此每个加法都只对单个 64 位浮点值进行运算)。 vaddsd 的延迟 = 3,吞吐量 = 0.5(每条指令周期数)。这意味着它可以在一个周期内开始执行前 2 次加法。然后在下一个循环中,它将开始第二对加法。因此,可以根据吞吐量为该循环实现 2 个周期/迭代。但是延迟,你记得是 3 个周期。所以这个循环在延迟上也有瓶颈。展开一次(使用 4 个额外的累加器;或者通过在将其添加到主累加器之前在彼此之间添加 xmm4-7 来中断循环内的循环携带的 dep.chain)以摆脱瓶颈(它可能会加快约 50%) .

请注意,此(“无 AVX”)反汇编仍在使用 VEX 编码,因此技术上仍需要支持 AVX 的 CPU。

关于基准测试

请注意,您的反汇编没有任何加载或存储,因此这可能代表也可能不代表添加 2 个 4 双向量数组的性能比较。

,

您正在处理延迟问题。根据 CPU,您必须等待 3 或 4 个周期,直到您可以使用 vaddpdvaddsd 指令的结果。但是在 1 个周期内最多可以执行 2 个 vaddpdvaddsd 指令(如果 CPU 不必等待源寄存器)。

因为在你的循环中

; Without avx
1080:   c5 fb 58 c4             vaddsd %xmm4,%rax
1094:   75 ea                   jne    1080

每个 vaddsd 取决于前一次迭代的结果,它必须等待 3 或 4 个周期才能执行此操作。但是所有 vaddsd 以及 subjne 的执行都可能在这段时间内发生。因此,对于这个简单的循环,执行一个 vaddpd 或四个 vaddsd 并没有什么区别。

要完全耗尽 vaddpd 指令,您需要执行其中的 6 或 8 条不依赖彼此的结果(或有其他独立工作的指令)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。