Вот рабочее решение, использующее временный массив:
__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 делает несколько сумасшедших подстановок (хотя и не уверен, что они на самом деле имеют значение).
Код может быть расширен для сдвига произвольной последовательности регистров (всегда переносящих байты из предыдущего регистра).