Горизонтальная сумма 32-битных чисел с плавающей запятой в 256-битном векторе AVX

У меня есть два массива с плавающей запятой, и я хотел бы рассчитать скалярное произведение, используя SSE и AVX, с минимально возможной задержкой. Я знаю, что для поплавков существует 256-битный точечный продукт, но я читал на SO, что это медленнее, чем метод, описанный ниже: (https://stackoverflow.com/a/4121295/997112).

Я сделал большую часть работы, вектор temp_sums содержит все суммы, мне просто нужно просуммировать все восемь 32-битных сумм, содержащихся в temp_sum в конце.

#include "xmmintrin.h"
#include "immintrin.h"

int main(){
    const int num_elements_in_array = 16;
    __declspec(align(32)) float x[num_elements_in_array];
    __declspec(align(32)) float y[num_elements_in_array];

    x[0] = 2;   x[1] = 2;   x[2] = 2;   x[3] = 2;
    x[4] = 2;   x[5] = 2;   x[6] = 2;   x[7] = 2;
    x[8] = 2;   x[9] = 2;   x[10] = 2;  x[11] = 2;
    x[12] = 2;  x[13] = 2;  x[14] = 2;  x[15] = 2;

    y[0] = 3;   y[1] = 3;   y[2] = 3;   y[3] = 3;
    y[4] = 3;   y[5] = 3;   y[6] = 3;   y[7] = 3;
    y[8] = 3;   y[9] = 3;   y[10] = 3;  y[11] = 3;
    y[12] = 3;  y[13] = 3;  y[14] = 3;  y[15] = 3;

    __m256 a;
    __m256 b;
    __m256 temp_products;   
    __m256 temp_sum = _mm256_setzero_ps();

    unsigned short j = 0;
    const int sse_data_size = 32;
    int num_values_to_process = sse_data_size/sizeof(float);

    while(j < num_elements_in_array){
        a = _mm256_load_ps(x+j);
        b = _mm256_load_ps(y+j);

        temp_products = _mm256_mul_ps(b, a);
        temp_sum = _mm256_add_ps(temp_sum, temp_products);

        j = j + num_values_to_process;
    }

    //Need to "process" temp_sum as a final value here

}

Я беспокоюсь, что 256-битные встроенные функции, которые мне нужны, недоступны до AVX 1.


person user997112    schedule 21.04.2014    source источник
comment
Вот как я бы это сделал 32-битные числа с плавающей запятой"> stackoverflow.com/questions/13879609/   -  person Z boson    schedule 22.04.2014


Ответы (2)


Я бы предложил использовать 128-битные инструкции AVX, когда это возможно. Это уменьшит задержку одного междоменного перемешивания (2 цикла на Intel Sandy/Ivy Bridge) и повысит эффективность процессоров, которые выполняют инструкции AVX на 128-битных исполнительных устройствах (в настоящее время AMD Bulldozer, Piledriver, Steamroller и Jaguar):

static inline float _mm256_reduce_add_ps(__m256 x) {
    /* ( x3+x7, x2+x6, x1+x5, x0+x4 ) */
    const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(x, 1), _mm256_castps256_ps128(x));
    /* ( -, -, x1+x3+x5+x7, x0+x2+x4+x6 ) */
    const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));
    /* ( -, -, -, x0+x1+x2+x3+x4+x5+x6+x7 ) */
    const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));
    /* Conversion to float is a no-op on x86-64 */
    return _mm_cvtss_f32(x32);
}
person Marat Dukhan    schedule 21.04.2014
comment
Правильно ли _mm_cvtf128_f32? Я не вижу этого во встроенном руководстве Intel: software.intel.com/sites/landingpage/IntrinsicsGuide - person user997112; 21.04.2014
comment
Да, поддерживается всеми основными компиляторами (icc, gcc, clang, msvc) - person Marat Dukhan; 21.04.2014
comment
Спасибо. Вы сказали, что я бы предложил использовать 128-битные инструкции AVX, когда это возможно. Я не думал, что можно будет использовать 128-битные инструкции для 256-битных регистров. Каково общее правило, когда это можно сделать? - person user997112; 21.04.2014
comment
Вы уверены, что он поддерживается всеми компиляторами? Я использую ICC 13, и он не компилируется - поиск в Google его тоже не показывает... - person user997112; 21.04.2014
comment
Вы правы, внутреннюю часть следует называть _mm_cvtss_f32 - person Marat Dukhan; 21.04.2014
comment
@MaratDukhan и повысить эффективность процессоров, которые выполняют инструкции AVX на 128-битных исполнительных устройствах. Вы имеете в виду, что на этих процессорах менее эффективно использовать AVX, чем использовать SSE? Это потому, что AVX более ограничительный (связывает две 128-битные полосы вместе, а не позволяет им быть независимыми). - person Z boson; 22.04.2014
comment
@Zboson On Bulldozer AVX-256 часто менее эффективен, чем AVX-128, из-за недостатков декодера инструкций. На других процессорах AVX более эффективен из-за меньшей нагрузки на декодеры инструкций (часто узкое место), даже несмотря на то, что инструкции AVX-256 внутренне разложены на 2 микрооперации. - person Marat Dukhan; 22.04.2014
comment
Не приведет ли использование инструкций sse в контексте AVX к довольно большому штрафу без vzeroupper? - person Pixelchemist; 09.11.2016
comment
Это было бы так, но 128-битные встроенные функции SSE будут генерировать 128-битные инструкции AVX, а не инструкции SSE, при нацеливании на наборы инструкций AVX. - person Marat Dukhan; 21.11.2016

Вы можете эмулировать полное горизонтальное добавление с помощью AVX (то есть правильной 256-битной версии _mm256_hadd_ps) следующим образом:

#define _mm256_full_hadd_ps(v0, v1) \
        _mm256_hadd_ps(_mm256_permute2f128_ps(v0, v1, 0x20), \
                       _mm256_permute2f128_ps(v0, v1, 0x31))

Если вы просто работаете с одним входным вектором, вы можете немного упростить это.

person Paul R    schedule 21.04.2014
comment
Спасибо за Ваш ответ. Я работаю только с одним вектором - как это упростить? Задержка низкая? - person user997112; 21.04.2014
comment
Вы, вероятно, захотите упростить его в контексте того, что вы делаете в то же время (в данном случае, предположительно, просто сумма горизонтального сокращения). Вышеупомянутая реализация является общей заменой нативного _mm256_hadd_ps, который ведет себя так, как вы могли бы ожидать для полной 256-битной реализации SIMD (а не 2x128-битного кладжа SIMD, который вы получаете с AVX всякий раз, когда задействованы горизонтальные операции). Он был протестирован, и я предлагаю использовать его как есть на данный момент, а затем рассмотреть возможность его упрощения/оптимизации только в случае необходимости. - person Paul R; 21.04.2014