C++ 获得8个源向量水平和的_m256的最有效方法

C++ 获得8个源向量水平和的_m256的最有效方法,c++,matrix,sum,sse,avx,C++,Matrix,Sum,Sse,Avx,我知道如何将一个\uuuu m256求和得到一个求和值。但是,我有8个向量,比如 输入 输出 a[0]+a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7], ...., h[0]+h[1]+h[2]+h[3]+h[4]+h[5]+h[6]+h[7] 我的方法。好奇是否有更好的方法 __m256 sumab = _mm256_hadd_ps(accumulator1, accumulator2); __m256 sumcd

我知道如何将一个
\uuuu m256
求和得到一个求和值。但是,我有8个向量,比如 输入

输出

a[0]+a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7], 
 ...., 
h[0]+h[1]+h[2]+h[3]+h[4]+h[5]+h[6]+h[7]
我的方法。好奇是否有更好的方法

            __m256 sumab = _mm256_hadd_ps(accumulator1, accumulator2);
            __m256 sumcd = _mm256_hadd_ps(accumulator3, accumulator4);

            __m256 sumef = _mm256_hadd_ps(accumulator5, accumulator6);
            __m256 sumgh = _mm256_hadd_ps(accumulator7, accumulator8);

            __m256 sumabcd = _mm256_hadd_ps(sumab, sumcd);
            __m256 sumefgh = _mm256_hadd_ps(sumef, sumgh);

            __m128 sumabcd1 = _mm256_extractf128_ps(sumabcd, 0);
            __m128 sumabcd2 = _mm256_extractf128_ps(sumabcd, 1);
            __m128 sumefgh1 = _mm256_extractf128_ps(sumefgh, 0);
            __m128 sumefgh2 = _mm256_extractf128_ps(sumefgh, 1);

            sumabcd1 = _mm_add_ps(sumabcd1, sumabcd2);
            sumefgh1 = _mm_add_ps(sumefgh1, sumefgh2);

 __m256 result =_mm256_insertf128_ps(_mm256_castps128_ps256(sumabcd1), sumefgh1, 1)
更新:是(我认为)相同的问题,解决方法是一个混合替换一个_mm256_permute2f128_p。另一个答案是用更多混合替换随机UOP。用其中一个代替


最初的答案是,没有使用任何混合,将在洗牌上出现瓶颈 您可以使用2x
\u mm256\u permute2f128\u ps
排列垂直
vaddps的高低车道。这不是2x
extractf128
/
insertf128
。这也将两条128b
vaddps xmm
指令转换为一条256b
vaddps ymm

vperm2f128
与英特尔CPU上的单个
vextractf128
vinsertf128
一样快。不过,AMD的速度很慢(推土机系列的8M-ops和4c延迟)。尽管如此,即使你关心AMD的性能,你也需要避免它。(其中一个permutes实际上可以是
vinsertf128


这个。第二个
permute2f128
实际上编译成一个
vinsertf128
,因为它只以与
vinsertf128
相同的方式使用每个输入的低端通道。GCC4.7及更高版本进行了此优化,但只有更新的clang版本(v3.7)进行了此优化。如果您关心旧的叮当声,请在源代码级别执行此操作

源代码行中的节省量大于指令中的节省量,因为
\u mm256\u extractf128\u ps(sumbacd,0)编译为零指令:这只是一个强制转换。除了
1
之外,任何编译器都不应使用imm8发出
vextractf128
。(
vmovdqa-xmm/m128,xmm
对于获得低车道总是更好)。很好,Intel在未来的验证中浪费了一个指令字节,而您不能使用它,因为普通的VEX前缀没有空间来编码更长的向量

两条
vaddps-xmm
指令可以并行运行,因此使用一条
vaddps-ymm
指令主要是增加吞吐量(和代码大小),而不是延迟

但是,我们确实从完全消除最终的
vinsertf128
中减少了3个周期


vhaddps
是3个uops,5c延迟,每2c吞吐量一个。(天湖上的延迟为6c)。这三个UOP中的两个运行在shuffle端口上。我猜它基本上是在做2x
shufps
来为
addps
生成操作数

如果我们可以用一个
shufps
/
addps
或其他东西来模拟
haddps
(或者至少得到一个我们可以使用的水平操作),我们就会走在前面。不幸的是,我不知道该怎么做。一次洗牌只能产生一个结果,其中包含来自两个向量的数据,但我们需要两个输入到垂直
addps
才能包含来自两个向量的数据


我不认为以另一种方式进行水平求和看起来很有希望,因为公共水平求和用例只关心其输出的一个元素。这里不是这样的:每个
hadd
结果的每个元素实际上都被使用了。

我不认为您可以在这方面有很大的改进,但是如果它真的对性能至关重要,那么请注意
\u mm256\u hadd\u ps
的延迟通常为5,而
\u mm256\u add\u ps
的延迟为3,因此,当你有选择的时候,也许更喜欢后者,即使它增加了一些说明。英特尔的IACA工具可以用来比较像这样的小代码片段的相对效率。那么你需要8个源向量的水平和向量吗?您可以先进行转置,然后进行垂直求和,但这可能要慢得多。看看你的想法。使用除HADDP之外的INSN做一些工作可能是好的。OTOH,您在每一步都充分利用了
hadd
的合并功能,从不在两个操作数相同的情况下使用它
extract(x,0)
是免费的,因为它只是一个强制转换。也许你可以重新考虑你的算法,这样你就只需要使用垂直运算符进行矩阵乘法。我不认为有一种方法可以在不将内环乘法从8x8转换为1x8的情况下,在内环中进行纯垂直运算符,而内环乘法的比例为O(n^3)而求和运算是在一个只缩放O(n^2)的部分中。我只是想创建一个用户友好的多线程c++11矩阵库,它不一定是单核上最快的。我使用AVX2的速度比Eigen快约30%,使用SSE2的速度则慢约20%。坦率地说,我认为区别在于缓存大小优化,在看了Eigen源代码之后。感谢启动AVX之后,我仍然对一些排列和洗牌操作感到困惑,因为它们不像旧的SSE寄存器那样直截了当。每当我使用“提取”时,我知道洗牌几乎总是有一种更好的方法,但我想不出来。非常感谢。@user2927848:在某些情况下,插入/提取是有用的。e、 g.作为水平总和(或其他减少)的第一步。AVX很难,因为车道内的东西比跨车道的东西延迟低。(有些东西,如交叉车道置换(
vpermps
)需要AVX2)。
            __m256 sumab = _mm256_hadd_ps(accumulator1, accumulator2);
            __m256 sumcd = _mm256_hadd_ps(accumulator3, accumulator4);

            __m256 sumef = _mm256_hadd_ps(accumulator5, accumulator6);
            __m256 sumgh = _mm256_hadd_ps(accumulator7, accumulator8);

            __m256 sumabcd = _mm256_hadd_ps(sumab, sumcd);
            __m256 sumefgh = _mm256_hadd_ps(sumef, sumgh);

            __m128 sumabcd1 = _mm256_extractf128_ps(sumabcd, 0);
            __m128 sumabcd2 = _mm256_extractf128_ps(sumabcd, 1);
            __m128 sumefgh1 = _mm256_extractf128_ps(sumefgh, 0);
            __m128 sumefgh2 = _mm256_extractf128_ps(sumefgh, 1);

            sumabcd1 = _mm_add_ps(sumabcd1, sumabcd2);
            sumefgh1 = _mm_add_ps(sumefgh1, sumefgh2);

 __m256 result =_mm256_insertf128_ps(_mm256_castps128_ps256(sumabcd1), sumefgh1, 1)
__m256 hsum8(__m256 a, __m256 b, __m256 c, __m256 d,
             __m256 e, __m256 f, __m256 g, __m256 h)
{
    // a = [ A7 A6 A5 A4 | A3 A2 A1 A0 ]
    __m256 sumab = _mm256_hadd_ps(a, b);
    __m256 sumcd = _mm256_hadd_ps(c, d);

    __m256 sumef = _mm256_hadd_ps(e, f);
    __m256 sumgh = _mm256_hadd_ps(g, h);

    __m256 sumabcd = _mm256_hadd_ps(sumab, sumcd);  // [ D7:4 ... A7:4 | D3:0 ... A3:0 ]
    __m256 sumefgh = _mm256_hadd_ps(sumef, sumgh);  // [ H7:4 ... E7:4 | H3:0 ... E3:0 ]

    __m256 sum_hi = _mm256_permute2f128_ps(sumabcd, sumefgh, 0x31);  // [ H7:4 ... E7:4 | D7:4 ... A7:4 ]
    __m256 sum_lo = _mm256_permute2f128_ps(sumabcd, sumefgh, 0x20);  // [ H3:0 ... E3:0 | D3:0 ... A3:0 ]

    __m256 result = _mm256_add_ps(sum_hi, sum_lo);
    return result;
}