SIMD minmag 和 maxmag
SIMD minmag and maxmag
我想实现 SIMD minmag and maxmag functions。据我了解这些功能是
minmag(a,b) = |a|<|b| ? a : b
maxmag(a,b) = |a|>|b| ? a : b
我想要这些用于 float 和 double,我的目标硬件是 Haswell。我真正需要的是计算两者的代码。这是我的 SSE4.1 for double(AVX 代码几乎相同)
static inline void maxminmag(__m128d & a, __m128d & b) {
__m128d mask = _mm_castsi128_pd(_mm_setr_epi32(-1,0x7FFFFFFF,-1,0x7FFFFFFF));
__m128d aa = _mm_and_pd(a,mask);
__m128d ab = _mm_and_pd(b,mask);
__m128d cmp = _mm_cmple_pd(ab,aa);
__m128d cmpi = _mm_xor_pd(cmp, _mm_castsi128_pd(_mm_set1_epi32(-1)));
__m128d minmag = _mm_blendv_pd(a, b, cmp);
__m128d maxmag = _mm_blendv_pd(a, b, cmpi);
a = maxmag, b = minmag;
}
但是,这并不像我希望的那样有效。 是否有更好的方法或至少值得考虑的替代方法?我想尽量避免使用端口 1,因为我已经有很多 additions/subtractions 使用该端口。 _mm_cmple_pd
内部函数转到端口 1。
我感兴趣的主要功能是这个:
//given |a| > |b|
static inline doubledouble4 quick_two_sum(const double4 & a, const double4 & b) {
double4 s = a + b;
double4 e = b - (s - a);
return (doubledouble4){s, e};
}
所以我真正追求的是这个
static inline doubledouble4 two_sum_MinMax(const double4 & a, const double4 & b) {
maxminmag(a,b);
return quick_to_sum(a,b);
}
编辑:我的目标是 two_sum_MinMax
比下面的 two_sum
更快:
static inline doubledouble4 two_sum(const double4 &a, const double4 &b) {
double4 s = a + b;
double4 v = s - a;
double4 e = (a - (s - v)) + (b - v);
return (doubledouble4){s, e};
}
编辑:这是我追求的终极功能。它执行 20 add/subs 所有这些都转到 Haswell 上的端口 1。在这个问题中使用我的 two_sum_MinMax
实现将它降低到端口 1 上的 16 add/subs 但它的延迟更差并且仍然更慢。您可以查看此函数的程序集,并在 optimize-for-fast-multiplication-but-slow-addition-fma-and-doubledouble 阅读更多关于我为什么关心它的信息
static inline doublefloat4 adddd(const doubledouble4 &a, const doubledouble4 &b) {
doubledouble4 s, t;
s = two_sum(a.hi, b.hi);
t = two_sum(a.lo, b.lo);
s.lo += t.hi;
s = quick_two_sum(s.hi, s.lo);
s.lo += t.lo;
s = quick_two_sum(s.hi, s.lo);
return s;
// 2*two_sum, 2 add, 2*quick_two_sum = 2*6 + 2 + 2*3 = 20 add
}
这是使用较少指令的替代实现:
static inline void maxminmag_test(__m128d & a, __m128d & b) {
__m128d cmp = _mm_add_pd(a, b); // test for mean(a, b) >= 0
__m128d amin = _mm_min_pd(a, b);
__m128d amax = _mm_max_pd(a, b);
__m128d minmag = _mm_blendv_pd(amin, amax, cmp);
__m128d maxmag = _mm_blendv_pd(amax, amin, cmp);
a = maxmag, b = minmag;
}
它使用了一种有点微妙的算法(见下文),结合我们可以使用符号位作为选择掩码的事实。
也采用了@EOF的建议,只使用一个掩码,切换操作数顺序,省了一条指令。
我已经用少量案例对其进行了测试,它似乎与您的原始实现相匹配。
算法:
if (mean(a, b) >= 0) // this can just be reduced to (a + b) >= 0
{
minmag = min(a, b);
maxmag = max(a, b);
}
else
{
minmag = max(a, b);
maxmag = min(a, b);
}
我想实现 SIMD minmag and maxmag functions。据我了解这些功能是
minmag(a,b) = |a|<|b| ? a : b
maxmag(a,b) = |a|>|b| ? a : b
我想要这些用于 float 和 double,我的目标硬件是 Haswell。我真正需要的是计算两者的代码。这是我的 SSE4.1 for double(AVX 代码几乎相同)
static inline void maxminmag(__m128d & a, __m128d & b) {
__m128d mask = _mm_castsi128_pd(_mm_setr_epi32(-1,0x7FFFFFFF,-1,0x7FFFFFFF));
__m128d aa = _mm_and_pd(a,mask);
__m128d ab = _mm_and_pd(b,mask);
__m128d cmp = _mm_cmple_pd(ab,aa);
__m128d cmpi = _mm_xor_pd(cmp, _mm_castsi128_pd(_mm_set1_epi32(-1)));
__m128d minmag = _mm_blendv_pd(a, b, cmp);
__m128d maxmag = _mm_blendv_pd(a, b, cmpi);
a = maxmag, b = minmag;
}
但是,这并不像我希望的那样有效。 是否有更好的方法或至少值得考虑的替代方法?我想尽量避免使用端口 1,因为我已经有很多 additions/subtractions 使用该端口。 _mm_cmple_pd
内部函数转到端口 1。
我感兴趣的主要功能是这个:
//given |a| > |b|
static inline doubledouble4 quick_two_sum(const double4 & a, const double4 & b) {
double4 s = a + b;
double4 e = b - (s - a);
return (doubledouble4){s, e};
}
所以我真正追求的是这个
static inline doubledouble4 two_sum_MinMax(const double4 & a, const double4 & b) {
maxminmag(a,b);
return quick_to_sum(a,b);
}
编辑:我的目标是 two_sum_MinMax
比下面的 two_sum
更快:
static inline doubledouble4 two_sum(const double4 &a, const double4 &b) {
double4 s = a + b;
double4 v = s - a;
double4 e = (a - (s - v)) + (b - v);
return (doubledouble4){s, e};
}
编辑:这是我追求的终极功能。它执行 20 add/subs 所有这些都转到 Haswell 上的端口 1。在这个问题中使用我的 two_sum_MinMax
实现将它降低到端口 1 上的 16 add/subs 但它的延迟更差并且仍然更慢。您可以查看此函数的程序集,并在 optimize-for-fast-multiplication-but-slow-addition-fma-and-doubledouble 阅读更多关于我为什么关心它的信息
static inline doublefloat4 adddd(const doubledouble4 &a, const doubledouble4 &b) {
doubledouble4 s, t;
s = two_sum(a.hi, b.hi);
t = two_sum(a.lo, b.lo);
s.lo += t.hi;
s = quick_two_sum(s.hi, s.lo);
s.lo += t.lo;
s = quick_two_sum(s.hi, s.lo);
return s;
// 2*two_sum, 2 add, 2*quick_two_sum = 2*6 + 2 + 2*3 = 20 add
}
这是使用较少指令的替代实现:
static inline void maxminmag_test(__m128d & a, __m128d & b) {
__m128d cmp = _mm_add_pd(a, b); // test for mean(a, b) >= 0
__m128d amin = _mm_min_pd(a, b);
__m128d amax = _mm_max_pd(a, b);
__m128d minmag = _mm_blendv_pd(amin, amax, cmp);
__m128d maxmag = _mm_blendv_pd(amax, amin, cmp);
a = maxmag, b = minmag;
}
它使用了一种有点微妙的算法(见下文),结合我们可以使用符号位作为选择掩码的事实。
也采用了@EOF的建议,只使用一个掩码,切换操作数顺序,省了一条指令。
我已经用少量案例对其进行了测试,它似乎与您的原始实现相匹配。
算法:
if (mean(a, b) >= 0) // this can just be reduced to (a + b) >= 0
{
minmag = min(a, b);
maxmag = max(a, b);
}
else
{
minmag = max(a, b);
maxmag = min(a, b);
}