英特尔高级矢量扩展(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
含有之间的点积的结果四个浮点矢量val1
和val0
, val2
和val0
, val3
和val0
, val4
和val0
。
或许这能够给您的建议提示?
我会用一个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 );
我将延长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
。
对于单点的产品,这是一个简单的乘法垂直和水平总和(见做在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
内部洗牌xy
和zw
两种不同的方式在一起,提要,以垂直addpd
,而这正是我们要手工做的反正。 如果我们保持xy
和zw
独立的,我们就需要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