Почему __m256 вместо 'float' дает производительность более чем в 8 раз? - PullRequest
0 голосов
/ 28 января 2019

Почему я получаю такое огромное ускорение (в 16 раз), используя __m256 тип данных?За один раз обрабатывается 8 чисел с плавающей запятой, поэтому я ожидаю увидеть только ускорение x8?

Мой процессор - 4-ядерный Devil Canyon i7 (с гиперпоточностью) Компиляция с Visual Studio 2017 в режиме выпуска - оптимизация O2включено.

Быстрая версия потребляет 0,000151 секунды на матрице 400x400:

//make this matrix only keep the signs of its entries
inline void to_signs() {

    __m256 *i = reinterpret_cast<__m256*>(_arrays);
    __m256 *end = reinterpret_cast<__m256*>(_arrays + arraysSize());

    __m256 maskPlus = _mm256_set1_ps(1.f);
    __m256 maskMin =  _mm256_set1_ps(-1.f);

    //process the main portion of the array.  NOTICE: size might not be divisible by 8:
    while(true){
        ++i;
        if(i > end){  break; }

        __m256 *prev_i = i-1;
        *prev_i = _mm256_min_ps(*prev_i, maskPlus);
        *prev_i = _mm256_max_ps(*prev_i, maskMin);
    }

    //process the few remaining numbers, at the end of the array:
    i--;
    for(float *j=(float*)i; j<_arrays+arraysSize(); ++j){
        //taken from here:http://www.musicdsp.org/showone.php?id=249
        // mask sign bit in f, set it in r if necessary:
        float r = 1.0f;
        (int&)r |= ((int&)(*j) & 0x80000000);//according to author, can end up either -1 or 1 if zero.
        *j = r;
    }
}

старшая версия, работает с 0,002416 секундами:

inline void to_signs_slow() {
    size_t size = arraysSize();

    for (size_t i = 0; i<size; ++i) {
        //taken from here:http://www.musicdsp.org/showone.php?id=249
        // mask sign bit in f, set it in r if necessary:

        float r = 1.0f;
        (int&)r |= ((int&)_arrays[i] & 0x80000000);//according to author, can end up either -1 or 1 if zero.
        _arrays[i] = r;
    }
}

Это тайно использует 2 ядра, так что это преимущество исчезнет, ​​как только я начну использовать многопоточность?

Редактировать:

На матрице большего размера,с размером (10e6) x (4e4) я получаю в среднем 3 и 14 секунд.Так что простое ускорение x4, даже не x8 Это, вероятно, связано с пропускной способностью памяти, и вещи не помещаются в кэш

Тем не менее, мой вопрос о приятном сюрпризе ускорения x16:)

1 Ответ

0 голосов
/ 28 января 2019

Ваша скалярная версия выглядит ужасно (с приведением ссылок для определения типов) и, вероятно, компилируется в действительно неэффективный asm, который намного медленнее, чем копирование каждого 32-битного элемента в битовый шаблон для 1.0f.Для этого требуется только целое число И и ИЛИ, чтобы сделать это скалярным (если MSVC не смог автоматически векторизовать это для вас), но я не удивлюсь, если компилятор копирует его в регистр XMM или что-то в этом роде.


Ваша первая версия с векторизацией вручную даже не выполняет ту же работу, однако она просто маскирует все незнаковые биты, оставляя -0.0f или +0.0f.Таким образом, он будет компилироваться в один vandps ymm0, ymm7, [rdi] и один SIMD-магазин с vmovups [rdi], ymm0, плюс некоторые издержки цикла.

Не то, чтобы добавление _mm256_or_ps с set1(1.0f) замедляло бы его, вы все равноУзкое место в пропускной способности кэша или пропускной способности хранилища 1 в такт.


Затем вы отредактировали его до версии, которая ограничивается диапазоном -1.0f .. +1.0f, оставляя входные данные с величиной менее 1,0 неизмененными.Это не будет медленнее, чем две побитовые операции, за исключением того, что Haswell (каньон дьявола) запускает логические значения FP только на порту 5, в отличие от фактических данных FP на порту 0 или порту 1.

Особенно, если вы неДелая что-нибудь еще с вашими поплавками, вы на самом деле захотите использовать _si256 встроенные функции, чтобы использовать только целочисленные инструкции AVX2 для них, для большей скорости на Haswell.(Но тогда ваш код не может работать без AVX2.)

На Skylake и более поздних версиях логические значения FP могут использовать все 3 векторных порта ALU.(https://agner.org/optimize/ для таблиц инструкций и руководства по uarch.)

Ваш код должен выглядеть примерно так:

// outside the loop if you want
const __m256i ones = _mm256_castps_si256(_mm256_set1_ps(1.0f));

for (something ; p += whatever) {
    __m256i floats = _mm256_load_si256( (const __m256i*)p );
    __m256i signs = _mm256_and_si256(floats,  _mm256_set1_epi32(0x80000000));
    __m256i applied = _mm256_or_si256(signs, ones);
    _mm256_store_si256((__m256i*)p, applied);

}
...