c – 跨越AVX车道的最佳方式?

c – 跨越AVX车道的最佳方式?,第1张

概述有类似标题的问题,但我的问题涉及其他地方没有涉及的一个非常具体的用例. 我有4个__128d寄存器(x0,x1,x2,x3),我想在5 __256d寄存器(y0,y1,y2,y3,y4)中重新组合它们的内容,如下所示,准备其他计算: on entry: x0 contains {a0, a1} x1 contains {a2, a3} x2 contains {a4, a5 有类似标题的问题,但我的问题涉及其他地方没有涉及的一个非常具体的用例.

我有4个__128d寄存器(x0,x1,x2,x3),我想在5 __256d寄存器(y0,y1,y2,y3,y4)中重新组合它们的内容,如下所示,准备其他计算:

on entry:    x0 contains {a0,a1}    x1 contains {a2,a3}    x2 contains {a4,a5}    x3 contains {a6,a7}on exit:    y0 contains {a0,a1,a2,a3}    y1 contains {a1,a3,a4}    y2 contains {a2,a4,a5}    y3 contains {a3,a5,a6}    y4 contains {a4,a6,a7}

我在下面的实现很慢.有没有更好的办法?

y0 = _mm256_set_m128d(x1,x0);__m128d lo = _mm_shuffle_pd(x0,1);__m128d hi = _mm_shuffle_pd(x1,1);y1 = _mm256_set_m128d(hi,lo);y2 = _mm256_set_m128d(x2,x1);lo = hi;hi = _mm_shuffle_pd(x2,x3,1);y3 = _mm256_set_m128d(hi,lo);y4 = _mm256_set_m128d(x3,x2);
解决方法 通过寄存器中的输入,您可以使用5个随机指令执行此 *** 作:

> 3x vinsertf128通过连接每个2 xmm寄存器来创建y0,y2和y4.
在这些结果之间创建y1和y3> 2x vshufpd(车道内shuffle).

请注意,y0和y2的低通道包含a1和a2,即y1的低通道所需的元素.同样的洗牌也适用于高速公路.

#include <immintrin.h>voID merge(__m128d x0,__m128d x1,__m128d x2,__m128d x3,__m256d *__restrict y0,__m256d *__restrict y1,__m256d *__restrict y2,__m256d *__restrict y3,__m256d *__restrict y4){    *y0 = _mm256_set_m128d(x1,x0);    *y2 = _mm256_set_m128d(x2,x1);    *y4 = _mm256_set_m128d(x3,x2);    // take the high element from the first vector,low element from the 2nd.    *y1 = _mm256_shuffle_pd(*y0,*y2,0b0101);    *y3 = _mm256_shuffle_pd(*y2,*y4,0b0101);}

汇编很好(with gcc and clang -O3 -march=haswell on Godbolt)到:

merge(double __vector(2),double __vector(2),double __vector(4)*,double __vector(4)*):    vinsertf128     ymm0,ymm0,xmm1,0x1    vinsertf128     ymm3,ymm2,xmm3,0x1    vinsertf128     ymm1,ymm1,xmm2,0x1    # vmovapd YMMWORD PTR [rdi],ymm0    vshufpd ymm0,5    # vmovapd YMMWORD PTR [rdx],ymm1    vshufpd ymm1,ymm3,5    # vmovapd YMMWORD PTR [r8],ymm3    # vmovapd YMMWORD PTR [rsi],ymm0    # vmovapd YMMWORD PTR [rcx],ymm1    # vzeroupper    # ret

我评论了商店和内联中会消失的东西,所以我们确实只有5个随机指令,而不是你问题中代码的9个随机指令. (还包括在Godbolt编译器资源管理器链接中).

这在AMD上是非常好的,其中vinsertf128非常便宜(因为256位寄存器实现为2x 128位半,所以它只是一个128位的副本而不需要特殊的shuffle端口.)256位的交叉路口AMD的shuffle速度很慢,但像vshufpd这样的内置256位shuffle只有2 uops.

在英特尔它是相当不错的,但主流的带有AVX的英特尔cpu在256位或FP shuffle中每个时钟的随机吞吐量只有1个. (SandybrIDge和之前的整数128位shuffle有更多的吞吐量,但AVX2 cpu减少了额外的shuffle单位,但他们无论如何都没有帮助.)

因此,英特尔cpu根本无法利用指令级并行性,但总共只有5微秒,这很不错.这是可能的最小值,因为您需要5个结果.

但特别是如果周围的代码也在洗牌时出现瓶颈,那么值得考虑的是只有4个存储和5个重叠向量加载的存储/重载策略.或者可能是2x vinsertf128来构造y0和y4,然后2x 256位存储3个重叠的重载.这可以让无序的exec开始使用y0或y4的依赖指令,而存储转发停顿解析为y1..3.

特别是如果你不太关心英特尔第一代SandybrIDge,其中未对齐的256位向量负载效率较低. (注意,如果你正在使用GCC,你想用gcc -mtune = haswell编译来关闭-mavx256-split-unaligned-load默认/ sandbrIDge调优.无论编译器如何,-march = native是一个如果让二进制文件在你编译它的机器上运行,那就好了,充分利用指令集和设置调优选项.)

但是如果来自前端的总uop吞吐量更多地是瓶颈所在,那么shuffle实现是最好的.

(有关性能调整的更多信息,请参阅0700和@L_404_1@中的其他性能链接.另外What considerations go into predicting latency for operations on modern superscalar processors and how can I calculate them by hand?,但真正的Agner Fog指南是一个更深入的指南,解释了吞吐量与延迟的实际关系.)

I do not even need to save,as data is also already available in contiguous memory.

然后简单地加载5个重叠负载几乎可以肯定是你能做的最有效的事情.

Haswell可以从L1d每个时钟执行2次加载,或者当任何跨越缓存行边界时更少.因此,如果您可以将块对齐64,那么完全没有缓存行分割就非常有效.缓存丢失很慢,但是从L1d缓存重新加载热数据非常便宜,而具有AVX支持的现代cpu通常具有高效的非对齐负载支持.

(就像我之前说过的,如果使用gcc,请确保使用-march = haswell或-mtune = haswell编译,而不仅仅是-mavx,以避免gcc的-mavx256-split-unaligned-load.)

4个负载1 vshufpd(y0,y2)可能是平衡负载端口压力和ALU压力的好方法,具体取决于周围代码中的瓶颈.如果周围的代码在随机端口压力较低时,甚至3次加载2次洗牌.

they are in registers from prevIoUs calculations which required them to be loaded.

如果先前的计算仍然在寄存器中有源数据,那么您可能首先完成256位加载,并且只使用它们的128位低半部分用于早期计算. (XMM寄存器是相应YMM寄存器的低128位,读取它们不会干扰上面的通道,因此_mm256_castpd256_pd128会将asm指令编译为零.)

对y0,y2和y4执行256位加载,并将它们的低半部分用作x0,x1和x2. (稍后使用未对齐的加载或随机播放构造y1和y3).

只有x3不是你想要的256位向量的低128位.

理想情况下,当您从同一地址执行_mm_loadu_pd和_mm256_loadu_pd时,编译器已经注意到此优化,但您可能需要通过执行 *** 作来手持它

__m256d y0 = _mm256_loadu_pd(base);__m128d x0 = _mm256_castpd256_pd128(y0);

等等,根据周围的代码,提取ALU内在(_mm256_extractf128_pd)或x3的128位加载.如果它只需要一次,那么让它折叠成一个内存 *** 作数,无论使用什么指令都可能是最好的.

潜在的缺点:在128位计算可以开始之前稍微延迟,或者如果256位负载是高速缓存线交叉而没有128位负载,则会有几个周期.但是如果您的数据块对齐了64个字节,则不会发生这种情况.

总结

以上是内存溢出为你收集整理的c – 跨越AVX车道的最佳方式?全部内容,希望文章能够帮你解决c – 跨越AVX车道的最佳方式?所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/1212149.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-04
下一篇 2022-06-04

发表评论

登录后才能评论

评论列表(0条)

保存