gcc 5.3 с -O3 -mavx -mtune=haswell
для x86-64 делает удивительно громоздкий код для обработки потенциально несогласованных входов для кода типа:
// convenient simple example of compiler input
// I'm not actually interested in this for any real program
void floatmul(float *a) {
for (int i=0; i<1024 ; i++)
a[i] *= 2;
}
clang использует нестандартные команды load/store, но gcc выполняет скалярное intro/outro и выровненный векторный цикл: он очищает первые до 7 неименованных итераций, полностью разворачивая их в последовательность
vmovss xmm0, DWORD PTR [rdi]
vaddss xmm0, xmm0, xmm0 ; multiply by two
vmovss DWORD PTR [rdi], xmm0
cmp eax, 1
je .L13
vmovss xmm0, DWORD PTR [rdi+4]
vaddss xmm0, xmm0, xmm0
vmovss DWORD PTR [rdi+4], xmm0
cmp eax, 2
je .L14
...
Это кажется довольно ужасным, особенно. для процессоров с кешем uop. Я сообщил об ошибке gcc об этом, предложив использовать более мелкий/лучший код, который gcc мог бы использовать при отслаивании неустановленных итераций. Это, вероятно, все еще не оптимально.
Этот вопрос касается того, что на самом деле было бы оптимальным с AVX. Я прошу об общих решениях, которые gcc и другие компиляторы могли/должны использовать. (Я не нашел ни одного списка рассылки gcc с обсуждением об этом, но долго не смотрел.)
Вероятно, будет много ответов, так как оптимальный для -mtune=haswell
, вероятно, будет отличаться от того, что оптимально для -mtune=bdver3
(каток). И тогда возникает вопрос о том, что оптимально при разрешении расширений набора инструкций (например, AVX2 для 256-битного целочисленного материала, BMI1 для перевода счетчика в битмаску за меньшее количество инструкций).
Я знаю об Agner Fog Optimizing Assembly guide, раздел 13.5. Доступ к несвязанным данным и частичным векторам. Он предлагает либо использовать неприглаженные обращения, делая перекрывающуюся запись в начале и/или конце, либо перетасовывая данные из выровненных доступов (но PALIGNR
принимает только число imm8, поэтому 2x pshufb
/por
). Он скидывает VMASKMOVPS
как не полезный, возможно, из-за того, насколько плохо он работает на AMD. Я подозреваю, что при настройке на Intel это стоит рассмотреть. Не очевидно, как создать правильную маску, поэтому заголовок вопроса.
Может получиться, что лучше просто использовать неприглаженные обращения, например clang. Для коротких буферов накладные расходы на выравнивание могут убьют любую выгоду от избежания расщепления кешлин для основного цикла. Для больших буферов, основной памяти или L3, поскольку узкое место может скрывать штраф за каскадные расщепления. Если у кого-то есть экспериментальные данные, чтобы поддержать это для любого реального кода, который они настроили, эта полезная информация тоже.
VMASKMOVPS
действительно подходит для целей Intel. (Версия SSE ужасна, с неявным невременным намеком, но в версии AVX этого нет. Существует даже новое свойство, чтобы убедиться, что вы не получите версию SSE для операндов 128b: _mm128_maskstore_ps
). Версия AVX немного медленнее на Haswell:
- 3 uops/4c latency/1-per-2c пропускная способность в качестве нагрузки.
- 4 часа /14 с задержка/1-на-2с пропускная способность в качестве хранилища 256b.
- 4 часа /13c задержка/1-на-1c пропускная способность в качестве хранилища 128b.
Форма хранения по-прежнему неэффективна на процессорах AMD, как Jaguar (1 на 22c tput), так и семейство Bulldozer: 1 на 16c на Steamroller (аналогично на Bulldozer) или 1 на пропускную способность по 180 c на Piledriver.
Но если мы хотим использовать VMASKMOVPS
, нам нужен вектор с высоким битом, установленным в каждом элементе, который должен быть действительно загружен/сохранен. PALIGNR и PSRLDQ (для использования на векторе all-ones) принимают только подсчеты времени компиляции.
Обратите внимание на то, что другие биты не имеют значения: он не обязательно должен быть однотипным, поэтому возможность рассеяния некоторых битов набора до высоких бит элементов является возможностью.