如果我有8个打包的32位浮点数(__m256
),那么提取所有8个元素的水平和的最快方法是什么?同样,如何获得水平最大值和最小值?换句话说,以下C ++函数的最佳实现是什么?
float sum(__m256 x); ///< returns sum of all 8 elements
float max(__m256 x); ///< returns the maximum of all 8 elements
float min(__m256 x); ///< returns the minimum of all 8 elements
答案 0 :(得分:6)
快速记下来(因此未经测试):
float sum(__m256 x) {
__m128 hi = _mm256_extractf128_ps(x, 1);
__m128 lo = _mm256_extractf128_ps(x, 0);
lo = _mm_add_ps(hi, lo);
hi = _mm_movehl_ps(hi, lo);
lo = _mm_add_ps(hi, lo);
hi = _mm_shuffle_ps(lo, lo, 1);
lo = _mm_add_ss(hi, lo);
return _mm_cvtss_f32(lo);
}
对于最小值/最大值,请将_mm_add_ps
和_mm_add_ss
替换为_mm_max_*
或_mm_min_*
。
请注意,这对于一些操作来说是很多工作; AVX并不是真正有效地进行水平操作。如果您可以批量处理多个向量,则可以使用更有效的解决方案。
答案 1 :(得分:4)
尽管Stephen Canon的回答可能是找到水平最大值/最小值的理想选择,但我认为可以找到更好的解决方案来解决水平和。
float horizontal_add (__m256 a) {
__m256 t1 = _mm256_hadd_ps(a,a);
__m256 t2 = _mm256_hadd_ps(t1,t1);
__m128 t3 = _mm256_extractf128_ps(t2,1);
__m128 t4 = _mm_add_ss(_mm256_castps256_ps128(t2),t3);
return _mm_cvtss_f32(t4);
}
答案 2 :(得分:3)
我尝试编写避免混合avx和非avx指令的代码,并且包含浮点数的avx寄存器的水平和可以通过
avx-only 完成vperm2f128
,vshufps
和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
和_mm_cvtss_f32
:
static inline float hadd(__m256 const& v)
{
return _mm_cvtss_f32(_mm256_castps256_ps128(hsums(v)));
}
我使用__rdtscp
针对其他解决方案做了一些基本的基准测试,并且没有找到一个在我的英特尔i5-2500k上的平均CPU周期计数方面更优越。
查看我发现的Agner Instruction Tables(对于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
我在哪里(由于值非常相似)没有一个明显优越(因为我无法预见指令计数,μop计数,延迟或吞吐量是否最重要)。
编辑,注意:我假设存在于下面的潜在问题不正确。
我怀疑,如果在ymm寄存器中得到结果就足够了 - 我的hsums
可能很有用,因为它不需要vzeroupper
来防止状态切换惩罚,因此可以与其他同时交错/执行avx计算使用不同的寄存器而不引入某种序列点。
答案 3 :(得分:-1)
union ymm {
__m256 m256;
struct {
__m128 m128lo;
__m128 m128hi;
};
};
union ymm result = {1,2,3,4,5,6,7,8};
__m256 a = {9,10,11,12,13,14,15,16};
result.m256 = _mm256_add_ps (result.m256, a);
result.m128lo = _mm_hadd_ps (result.m128lo, result.m128hi);
result.m128lo = _mm_hadd_ps (result.m128lo, result.m128hi);
result.m128lo = _mm_hadd_ps (result.m128lo, result.m128hi);