Горизонтальное добавление с __m512 (AVX512)

Как эффективно выполнить горизонтальное сложение с плавающей точкой в ​​512-битном регистре AVX (т.е. сложить элементы из одного вектора вместе)? Для 128- и 256-битных регистров это можно сделать с помощью _mm_hadd_ps и _mm256_hadd_ps, но _mm512_hadd_ps не существует. Документы Intel Intrinsics _mm512_reduce_add_ps. На самом деле она не соответствует ни одной инструкции, но ее существование говорит о том, что существует оптимальный метод, но, похоже, он не определен в заголовочных файлах, которые поставляются с последним снимком GCC, и я не могу найти определение для это с гуглом.

Я полагаю, что "hadd" можно эмулировать с помощью _mm512_shuffle_ps и _mm512_add_ps или я мог бы использовать _mm512_extractf32x4_ps, чтобы разбить 512-битный регистр на четыре 128-битных регистра, но я хочу убедиться, что я не пропустил что-то лучшее.

2 ответа

Решение

Компилятор INTEL имеет следующую внутреннюю функцию, определенную для выполнения горизонтальных сумм

_mm512_reduce_add_ps     //horizontal sum of 16 floats
_mm512_reduce_add_pd     //horizontal sum of 8 doubles
_mm512_reduce_add_epi32  //horizontal sum of 16 32-bit integers
_mm512_reduce_add_epi64  //horizontal sum of 8 64-bit integers

Однако, насколько я могу судить, они в любом случае разбиты на несколько инструкций, поэтому я не думаю, что вы получите что-то большее, чем выполнение горизонтальной суммы верхней и нижней части регистра AVX512.

__m256 low  = _mm512_castps512_ps256(zmm);
__m256 high = _mm256_castpd_ps(_mm512_extractf64x4_pd(_mm512_castps_pd(zmm),1));

__m256d low  = _mm512_castpd512_pd256(zmm);
__m256d high = _mm512_extractf64x4_pd(zmm,1);

__m256i low  = _mm512_castsi512_si256(zmm);
__m256i high = _mm512_extracti64x4_epi64(zmm,1);

Чтобы получить горизонтальную сумму, вы затем делаете sum = horizontal_add(low + high),

static inline 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);        
}

static inline double horizontal_add (__m256d a) {
    __m256d t1 = _mm256_hadd_pd(a,a);
    __m128d t2 = _mm256_extractf128_pd(t1,1);
    __m128d t3 = _mm_add_sd(_mm256_castpd256_pd128(t1),t2);
    return _mm_cvtsd_f64(t3);        
}

Я получил всю эту информацию и функции из библиотеки векторных классов Agner Fog и интерактивного справочника Intel Instrinsics.

Я дам Z бозону чек, поскольку пост отвечает на мой вопрос, но я думаю, что точную последовательность инструкций можно улучшить:

inline float horizontal_add(__m512 a) {
    __m512 tmp = _mm512_add_ps(a,_mm512_shuffle_f32x4(a,a,_MM_SHUFFLE(0,0,3,2)));
    __m128 r = _mm512_castps512_ps128(_mm512_add_ps(tmp,_mm512_shuffle_f32x4(tmp,tmp,_MM_SHUFFLE(0,0,0,1))));
    r = _mm_hadd_ps(r,r);
    return _mm_cvtss_f32(_mm_hadd_ps(r,r));
}

Горизонтальная сумма для двойной точности:

static inline double _mm512_horizontal_add(__m512d a){
    __m256d b = _mm256_add_pd(_mm512_castpd512_pd256(a), _mm512_extractf64x4_pd(a,1));
    __m128d d = _mm_add_pd(_mm256_castpd256_pd128(b), _mm256_extractf128_pd(b,1));
    double *f = (double*)&d;
    return _mm_cvtsd_f64(d) + f[1];
}

редактировать: примененные комментарии Питера Кордеса

Другие вопросы по тегам