英特尔 AVX:用于双精度浮点变量的 256 位版本点积
Intel AVX: 256-bits version of dot product for double precision floating point variables
英特尔高级矢量扩展指令集 (AVX( 在 256 位版本(YMM 寄存器(中不提供双精度浮点变量的点积。"为什么?"的问题已经在另一个论坛(这里(和Stack Overflow(这里(上得到了非常简短的处理。但是我面临的问题是如何以有效的方式用其他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 );
编辑:
在诺伯特·我扩展了这个版本,一次做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;
}
如果你只需要一个点积,这比英特尔上@hirschhornsalz的单向量答案要好,在 AMD 捷豹/推土机系列/Ryzen 上更大的胜利,因为它立即缩小到 128b,而不是做一堆 256b 的东西。 AMD 将 256b 操作拆分为两个 128b uop。
在
并行执行 2 或 4 个点积的情况下,值得使用 hadd
,其中您将其与 2 个不同的输入向量一起使用。 如果想要打包结果,Norbert 对两对向量的dot
看起来是最优的。 即使使用 AVX2 vpermpd
作为车道交叉洗牌,我也看不出有什么方法可以做得更好。
当然,如果您真的想要一个更大的dot
(8 或更多double
秒(,请使用垂直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
的成本是128b hadd
的2倍,将每个256b产品分别缩小到128b,然后与128b hadd结合使用可能是有意义的。
实际上,根据Agner Fog的表格,haddpd xmm,xmm
在Ryzen上是4 uops。 (256b ymm 版本是 8 uops(。 因此,如果数据正确,实际上最好在 Ryzen 上手动使用 2x vshufpd
+ vaddpd
。 可能不是:他的打桩机数据有3个uop haddpd xmm,xmm
,而只有4个uops的内存操作数。 对我来说,他们无法将hadd
实现为只有 3 个(或 ymm 的 6 个(uops 是没有意义的。
对于将结果打包到一个__m256d
中执行 4 dot
秒,提出的确切问题,我认为@hirschhornsalz的答案对于英特尔 CPU 来说看起来非常好。 我没有非常仔细地研究过它,但与hadd
成对组合是好的。 vperm2f128
在英特尔上是高效的(但在AMD上相当糟糕:在Ryzen上为8 uops,每3c吞吐量一个(。
- 算术运算的结果类似于:C浮点变量中的1/3
- 为什么双精度引用值在分配给C++中的浮点变量时不会更改
- 是否可以保证浮点变量的副本将按位等效于原始副本?
- 直接为浮点变量分配十六进制整数与通过指针转换分配之间的区别
- 为什么将浮点变量与另一个浮点变量的缩减为int给出了完全不同的答案
- 如何线性化两个浮点变量的乘积
- 在不同翻译单元中具有静态存储持续时间的依赖非局部常量浮点变量的常量初始化
- 比较 C++ 中的两个浮点变量
- 我创建了一个将雷亚尔转换为美元的程序,但浮点变量不正确
- 浮点变量不存储十进制
- 在 c++ 中,浮点变量除以整数变量时的结果类型
- 在C 中声明一个八倍精度浮点变量
- 有符号浮点变量转换为无符号DWORD
- 比较浮点变量
- 浮点变量变为无穷大
- 在C++中对浮点变量执行算术时,是否始终需要使用浮点数文字
- 当两个整数相乘并将结果存储在浮点变量中时,结果是否可以流动
- 在文本浏览器中打印Qt浮点变量
- 英特尔 AVX:用于双精度浮点变量的 256 位版本点积
- Intel AVX:为什么没有256位的双精度浮点变量点积版本?