英特尔AVX:双精度浮点变量积的256位版本(Intel AVX: 256-bits version

2019-06-24 04:43发布

英特尔高级矢量扩展(AVX)提供了双精度浮点变量的256位版本(YMM寄存器) 。 该“为什么?” 问题已经在另一个论坛(很简单的处理, 在这里 )和堆栈溢出( 在这里 )。 但我面临的问题是如何取代这个与其他AVX指令指令缺少一种有效的方式?

在256位版本的点积(存在单精度浮点变量这里参考 ):

 __m256 _mm256_dp_ps(__m256 m1, __m256 m2, const int mask);

我们的想法是找到这个失踪的指令的有效等价的:

 __m256d _mm256_dp_pd(__m256d m1, __m256d m2, const int mask);

更具体地讲,代码我想从改造__m128 (四个浮点)到__m256d (4个双打),请使用以下说明:

   __m128 val0 = ...; // Four float values
   __m128 val1 = ...; //
   __m128 val2 = ...; //
   __m128 val3 = ...; //
   __m128 val4 = ...; //

   __m128 res = _mm_or_ps( _mm_dp_ps(val1,  val0,   0xF1),
                _mm_or_ps( _mm_dp_ps(val2,  val0,   0xF2),
                _mm_or_ps( _mm_dp_ps(val3,  val0,   0xF4),
                           _mm_dp_ps(val4,  val0,   0xF8) )));

此代码的结果是一个_m128含有之间的点积的结果四个浮点矢量val1val0val2val0val3val0val4val0

或许这能够给您的建议提示?

Answer 1:

我会用一个4 *双乘法,然后hadd (不幸的是只增加了2 * 2漂浮在上,下半),提取的上半部分(洗牌应该平等合作,也许更快),并把它添加到下半。

其结果是在低64位的dotproduct

__m256d xy = _mm256_mul_pd( x, y );
__m256d temp = _mm256_hadd_pd( xy, xy );
__m128d hi128 = _mm256_extractf128_pd( temp, 1 );
__m128d dotproduct = _mm_add_pd( (__m128d)temp, hi128 );

编辑:
诺伯特·P的想法后,我扩展了这个版本在同一时间做4种的点产品。

__m256d xy0 = _mm256_mul_pd( x[0], y[0] );
__m256d xy1 = _mm256_mul_pd( x[1], y[1] );
__m256d xy2 = _mm256_mul_pd( x[2], y[2] );
__m256d xy3 = _mm256_mul_pd( x[3], y[3] );

// low to high: xy00+xy01 xy10+xy11 xy02+xy03 xy12+xy13
__m256d temp01 = _mm256_hadd_pd( xy0, xy1 );   

// low to high: xy20+xy21 xy30+xy31 xy22+xy23 xy32+xy33
__m256d temp23 = _mm256_hadd_pd( xy2, xy3 );

// low to high: xy02+xy03 xy12+xy13 xy20+xy21 xy30+xy31
__m256d swapped = _mm256_permute2f128_pd( temp01, temp23, 0x21 );

// low to high: xy00+xy01 xy10+xy11 xy22+xy23 xy32+xy33
__m256d blended = _mm256_blend_pd(temp01, temp23, 0b1100);

__m256d dotproduct = _mm256_add_pd( swapped, blended );


Answer 2:

我将延长drhirsch的回答在同一时间执行两个点的产品,节省了一些工作:

__m256d xy = _mm256_mul_pd( x, y );
__m256d zw = _mm256_mul_pd( z, w );
__m256d temp = _mm256_hadd_pd( xy, zw );
__m128d hi128 = _mm256_extractf128_pd( temp, 1 );
__m128d dotproduct = _mm_add_pd( (__m128d)temp, hi128 );

然后dot(x,y)是在低双和dot(z,w)是在高双dotproduct



Answer 3:

对于单点的产品,这是一个简单的乘法垂直和水平总和(见做在x86水平浮动矢量和最快的方式 )。 hadd收费2个+混洗的add 。 与两个输入=相同的载体一起使用时,它几乎总是亚最佳吞吐量。

// both elements = dot(x,y)
__m128d dot1(__m256d x, __m256d y) {
    __m256d xy = _mm256_mul_pd(x, y);

    __m128d xylow  = _mm256_castps256_pd128(xy);   // (__m128d)cast isn't portable
    __m128d xyhigh = _mm256_extractf128_pd(xy, 1);
    __m128d sum1 =   _mm_add_pd(xylow, xyhigh);

    __m128d swapped = _mm_shuffle_pd(sum1, sum1, 0b01);   // or unpackhi
    __m128d dotproduct = _mm_add_pd(sum1, swapped);
    return dotproduct;
}

如果你只需要一个点的产品,这是由英特尔1个洗牌UOP公司和AMD捷豹/推土机家族/ Ryzen更大的胜利比@ hirschhornsalz的单矢量回答更好,因为它缩小到128B的时候了,而不是做的一堆256B的东西。 AMD拆分256B OPS成两个128B微指令。


它可以使用值得hadd的情况下,像做2个或4个的点产品并行你使用它拥有两种不同的输入向量在哪里。 诺伯特的dot ,如果你想挤满了结果的两个向量对看起来最佳。 我没有看到任何方式甚至AVX2做的更好vpermpd作为车道交叉洗牌。

当然,如果你真的想要一个大dot (8个或更多double S),使用垂直add (具有多个蓄电池隐藏vaddps等待时间),并在年底做总结的水平。 您还可以使用fma (如果可用)。


haddpd内部洗牌xyzw两种不同的方式在一起,提要,以垂直addpd ,而这正是我们要手工做的反正。 如果我们保持xyzw独立的,我们就需要2个洗牌+ 2增加了对每一个来获取点积(在独立的寄存器)。 因此,通过一起洗牌他们hadd作为第一步,我们节省了洗牌的总数,只有增加和总UOP计数。

/*  Norbert's version, for an Intel CPU:
    __m256d temp = _mm256_hadd_pd( xy, zw );   // 2 shuffle + 1 add
    __m128d hi128 = _mm256_extractf128_pd( temp, 1 ); // 1 shuffle (lane crossing, higher latency)
    __m128d dotproduct = _mm_add_pd( (__m128d)temp, hi128 ); // 1 add
     // 3 shuffle + 2 add
*/

但对于AMD,其中vextractf128很便宜,256B hadd成本2倍之多128B hadd ,它可能是有意义的分别下降缩小每个256B产品128B,然后用128B哈德结合起来。

实际上,根据昂纳雾的表 , haddpd xmm,xmm是Ryzen 4个微指令。 (和256B YMM版本是8个微指令)。 因此,它实际上是更好地使用2X vshufpd + vaddpd手动上Ryzen,如果这些数据是正确的。 它可能不是:他的打桩机的数据有3 UOP haddpd xmm,xmm ,而且只有4微操作与内存操作数。 它没有任何意义,我认为他们无法实现hadd因为只有3个(或6青运)微操作。


对于做4 dot与打包成一个结果小号__m256d ,询问确切的问题,我觉得@ hirschhornsalz的答案查找英特尔CPU非常好。 我没有超仔细研究它,但在对相结合hadd是好的。 vperm2f128是英特尔高效的(但很糟糕的AMD:8个微指令上Ryzen每3C吞吐量之一)。



文章来源: Intel AVX: 256-bits version of dot product for double precision floating point variables