我尝试编写避免混合 avx 和非 avx 指令的代码,并且包含浮点数的 avx 寄存器的水平总和可以通过avx-only完成
- 1x
vperm2f128
,
- 2x
vshufps
和
- 3倍
vaddps
,
产生一个寄存器,其中所有条目都包含原始寄存器中所有元素的总和。
// permute
// 4, 5, 6, 7, 0, 1, 2, 3
// add
// 0+4, 1+5, 2+6, 3+7, 4+0, 5+1, 6+2, 7+3
// shuffle
// 1+5, 0+4, 3+7, 2+6, 5+1, 4+0, 7+3, 6+2
// add
// 1+5+0+4, 0+4+1+5, 3+7+2+6, 2+6+3+7,
// 5+1+4+0, 4+0+5+1, 7+3+6+2, 6+2+7+3
// shuffle
// 3+7+2+6, 2+6+3+7, 1+5+0+4, 0+4+1+5,
// 7+3+6+2, 6+2+7+3, 5+1+4+0, 4+0+5+1
// add
// 3+7+2+6+1+5+0+4, 2+6+3+7+0+4+1+5, 1+5+0+4+3+7+2+6, 0+4+1+5+2+6+3+7,
// 7+3+6+2+5+1+4+0, 6+2+7+3+4+0+5+1, 5+1+4+0+7+3+6+2, 4+0+5+1+6+2+7+3
static inline __m256 hsums(__m256 const& v)
{
auto x = _mm256_permute2f128_ps(v, v, 1);
auto y = _mm256_add_ps(v, x);
x = _mm256_shuffle_ps(y, y, _MM_SHUFFLE(2, 3, 0, 1));
x = _mm256_add_ps(x, y);
y = _mm256_shuffle_ps(x, x, _MM_SHUFFLE(1, 0, 3, 2));
return _mm256_add_ps(x, y);
}
_mm256_castps256_ps128
然后使用and很容易获得该值_mm_cvtss_f32
:
static inline float hadd(__m256 const& v)
{
return _mm_cvtss_f32(_mm256_castps256_ps128(hsums(v)));
}
我对其他解决方案进行了一些基本的基准测试__rdtscp
,但没有发现在我的英特尔 i5-2500k 上的平均 CPU 周期数方面更胜一筹。
查看我发现的Agner 指令表(对于 Sandy-Bridge 处理器):
µops lat. 1/tp count
this:
vperm2f128 1 2 1 1
vaddps 1 3 1 3
vshufps 1 1 1 2
sum 6 13 6 6
Z boson:
vhaddps 3 5 2 2
vextractf128 1 2 1 1
addss 1 3 1 1
sum 8 15 6 4
Stephen Canon:
vextractf128 1 2 1 1
addps 1 3 1 2
movhlps 1 1 1 1
shufps 1 1 1 1
addss 1 3 1 1
sum 8 13 6 6
对我来说(由于值非常相似)没有一个明显优于(因为我无法预见指令数、微操作数、延迟或吞吐量是否最重要)。
编辑,注意:我认为以下存在的潜在问题不正确。
我怀疑,如果在 ymm 寄存器中得到结果就足够了,myhsums
可能很有用,因为它不需要vzeroupper
防止状态切换惩罚,因此可以使用不同的寄存器与其他 avx 计算同时交错/执行,而无需引入某种序列点。