SIMD minmag и maxmag
Я хочу реализовать SIMD функции minmag и maxmag. Насколько я понимаю, эти функции
minmag(a,b) = |a|<|b| ? a : b
maxmag(a,b) = |a|>|b| ? a : b
Я хочу, чтобы они были для float и double, а мое целевое оборудование - Haswell. Мне действительно нужен код, который вычисляет оба. Вот что я имею для SSE4.1 для 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, так как у меня уже есть много дополнений/вычитаний с использованием этого порта. Экран _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 добавлений/подсетей, все из которых идут в порт 1 на Хасуэлл. Используя мою реализацию two_sum_MinMax
в этом вопросе, она доходит до 16 add/subs на порту 1, но имеет более низкую задержку и все еще медленнее. Вы можете увидеть сборку для этой функции и узнать больше о том, почему меня это волнует в оптимизировать для быстрого умножения, но медленного добавления-fma-и-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
}
Ответы
Ответ 1
Здесь альтернативная реализация, которая использует меньше инструкций:
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);
}