Эмуляция сдвигов на 64 байта с AVX-512 - PullRequest
3 голосов
/ 10 октября 2019

Мой вопрос является расширением предыдущего вопроса: Эмуляция сдвигов на 32 байта с AVX .

Как реализовать аналогичные сдвиги на 64 байта с AVX-512? В частности, как я должен реализовать:

  • __m512i _mm512_slli_si512(__m512i a, int imm8)
  • __m512i _mm512_srli_si512(__m512i a, int imm8)

Соответствует методам SSE2 _mm_slli_si128 и _mm_srli_si128.

1 Ответ

2 голосов
/ 15 октября 2019

Вот рабочее решение, использующее временный массив:

__m512i _mm512_slri_si512(__m512i a, size_t imm8)
{
    // set up temporary array and set upper half to zero 
    // (this needs to happen outside any critical loop)
    alignas(64) char temp[128];
    _mm512_store_si512(temp+64, _mm512_setzero_si512());

    // store input into lower half
    _mm512_store_si512(temp, a);

    // load shifted register
    return _mm512_loadu_si512(temp+imm8);
}

__m512i _mm512_slli_si512(__m512i a, size_t imm8)
{
    // set up temporary array and set lower half to zero 
    // (this needs to happen outside any critical loop)
    alignas(64) char temp[128];
    _mm512_store_si512(temp, _mm512_setzero_si512());

    // store input into upper half
    _mm512_store_si512(temp+64, a);

    // load shifted register
    return _mm512_loadu_si512(temp+(64-imm8));
}

Это также должно работать, если imm8 не было известно во время компиляции, но оно не выполняет никаких проверок за пределами допустимого. На самом деле вы можете использовать временный 3*64 и делить его между левым и правым методами сдвига (и оба будут работать и для отрицательных входных данных).

Конечно, если вы используете общий временный объект вне тела функции,Вы должны убедиться, что к нему не обращаются сразу несколько потоков.

Godbolt-Link с демонстрацией использования: https://godbolt.org/z/LSgeWZ


Как заметил Питер, этот магазинТрюк загрузки вызовет зависание переадресации на всех процессорах с AVX512 . Наиболее эффективный случай пересылки (задержка ~ 6 циклов) работает только тогда, когда все байты загрузки поступают из одного хранилища. Если загрузка выходит за пределы самого последнего хранилища, которое вообще перекрывает его, у нее есть дополнительная задержка (например, ~ 16 циклов) для сканирования буфера хранилища и, если необходимо, объединения байтов из кэша L1d. См. Могут ли современные реализации x86 выполнить перенос из нескольких предыдущих хранилищ? и Руководство по микроарху Agner Fog для получения более подробной информации. Этот процесс дополнительного сканирования, вероятно, может происходить для нескольких нагрузок параллельно и, по крайней мере, не останавливает другие вещи (например, обычную пересылку хранилища или остальную часть конвейера), поэтому он может не быть проблемой пропускной способности.

Если вам нужно много сдвигов сдвига для одних и тех же данных, хорошо подойдет одно хранилище и несколько повторных загрузок при разных выравниваниях.

Но если задержка является вашей основной проблемой, вам следует попробовать решение на основе valignd (также, если вы хотите сдвинуть кратно 4 байта, это, очевидно, более простое решение). Или для постоянного числа сдвигов может работать векторное управление для vpermw.


Для полноты, вот версия, основанная на valignd и valignr, работающих для сдвигов от 0 до 64,известен во время компиляции (с использованием C ++ 17 - но вы можете легко избежать if constexpr, это только здесь из-за static_assert). Вместо смещения в нули вы можете передать второй регистр (т. Е. Он будет вести себя так, как будто valignr будет вести себя, если он будет выровнен по линиям). а также вывод для каждой возможной операции shift_right: https://godbolt.org/z/xmKJvA

GCC точно переводит это в инструкции valignd и valignr - но может выполнять ненужную инструкцию vpxor (например, в * 1044)* пример), Clang делает несколько сумасшедших подстановок (хотя и не уверен, что они на самом деле имеют значение).

Код может быть расширен для сдвига произвольной последовательности регистров (всегда переносящих байты из предыдущего регистра).

...