Избегание переходных санкций AVX-SSE (VEX)
В нашем 64-битном приложении много кода (в частности, в стандартных библиотеках), которые используют регистры xmm0-xmm7 в режиме SSE.
Я хотел бы реализовать быструю копию памяти с помощью ymm-регистров. Я не могу изменить весь код, который использует регистры xmm, чтобы добавить префикс VEX, и я также думаю, что это нецелесообразно, так как он увеличит размер кода, может заставить его работать медленнее из-за необходимости процессора для декодирования больших инструкций.
Я просто хочу использовать два регистра ymm (и, возможно, zmm - доступные в продаже процессоры, поддерживающие zmm, будут доступны в этом году) для быстрой копии памяти.
Вопрос: как использовать регистры ymm, но избегать переходных штрафов?
Будет ли штраф, если я использую только регистры ymm8-ymm15 (а не ymm0-ymm7)? В SSE первоначально было восемь 128-битных регистров (xmm0-xmm7), но в 64-битном режиме (xmm8-xmm15) также доступны для инструкций с префиксом без VEX. Тем не менее, я рассмотрел наше 64-битное приложение, и он использует только xmm0-xmm7, так как он также имеет 32-битную версию с почти тем же кодом. Выполняется ли штраф только тогда, когда CPU пытается использовать регистр xmm, который использовался ранее как ymm, и имеет один из более высоких 128 бит, отличных от нуля? Разве не лучше просто обнулить регистры ymm, которые я использовал после быстрой копии памяти? Например, я однажды использовал регистр ymm для копирования 32 байтов памяти - какой самый быстрый способ его обнулить? Является ли "vpxor ymm15, ymm15, ymm15" достаточно быстро? (AFAIK, vpxor может выполняться на любом из трех портов выполнения ALU, p0/p1/p5, а vxorpd может выполняться только на p5). Не было бы время обнулить его больше, чем получить его, чтобы просто скопировать 32 байта памяти?
Ответы
Ответ 1
Другая возможность - использовать регистры zmm16 - zmm31. Эти регистры не имеют аналогов, отличных от VEX. Нет перехода состояния и нет штрафа за смешивание zmm16 - zmm31 с кодом не-VEX SSE. Эти 512-битные регистры доступны только в 64-битном режиме и только на процессорах с AVX512.
Ответ 2
Оптимальное решение, вероятно, должно перекомпилировать весь код с префиксами VEX. Кодированные инструкции VEX в основном имеют тот же размер, что и версии, отличные от VEX, из тех же инструкций, потому что инструкции, отличные от VEX, несут наследие большого количества префиксов и escape-кодов (из-за длинной истории близоруких патчей в инструкции схема кодирования). Префикс VEX объединяет все старые префиксы и escape-коды в один префикс двух или трех байтов (четыре байта для AVX512).
Переход VEX/non-VEX по-разному работает на разных процессорах (см. Почему этот код SSE в 6 раз медленнее без VZEROUPPER на Skylake?):
Старые процессоры Intel: инструкция VZEROUPPER необходима для чистого перехода между различными внутренними состояниями в процессоре.
В Intel Skylake или более поздних процессорах: VZEROUPPER необходим, чтобы избежать ложной зависимости инструкции, отличной от VEX, в верхней части регистра.
На современных процессорах AMD: 256-битный регистр обрабатывается как два 128-битных регистра. VZEROUPPER не требуется, кроме совместимости с процессорами Intel. Стоимость VZEROUPPER составляет около 6 тактов.
Преимущество использования префиксов VEX во всех ваших инструкциях заключается в том, что вы избегаете этих затрат перехода на всех процессорах. Ваш устаревший код может, вероятно, выиграть от некоторых 256-битных операций здесь и там в горячем внутреннем цикле.
Недостатком префиксов VEX является то, что код несовместим со старыми процессорами, поэтому вам может потребоваться сохранить старую версию для работы на старых процессорах
Ответ 3
Чтобы избежать штрафов на всех архитектурах, просто нужно выполнить vzeroall
или vzeroupper
после той части кода, которая использует инструкции, закодированные в VEX, перед возвратом к остальной части кода, которая использует инструкцию не-VEX.
Выполнение этих инструкций в любом случае считается хорошей практикой для всех подпрограмм, использующих AVX, и стоит дешево - за исключением, возможно, Knights Landing, но я сомневаюсь, что вы используете эту архитектуру. Даже если это так, характеристики производительности сильно отличаются от характеристик настольных компьютеров/семейства Xeon, поэтому вам, вероятно, все равно понадобится отдельная компиляция.
Это единственные инструкции, которые перемещаются из грязного верхнего в чистое верхнее состояние. Вы не можете просто обнулить определенные регистры, которые вы использовали, так как микросхема не отслеживает грязное состояние в каждом регистре.
Стоимость этих инструкций vzero*
составляет несколько циклов: поэтому, если то, что вы делаете в AVX, того стоит, оно, как правило, стоит того, чтобы оплатить эту небольшую стоимость.
Ответ 4
По моему опыту лучший способ Avoiding AVX-SSE (VEX) Transition Penalties
- позволить компилятору использовать собственный код микроархитектуры. Например, вы можете использовать SSE-Intrinsics
рядом с AVX-Intrinsics
и использовать -march=native
. Мой GCC 6.2
компилирует программу и использует инструкции VEX-Encoded
. Если вы видите сгенерированную сборку, вы найдете дополнительный v
перед всеми переведенными кодами SSE. С другой стороны, если вы сомневаетесь, вы можете использовать __asm__ __volatile__ ( "vzeroupper" : : : );
каждую точку своей программы, после использования регистров ymm
, но вы должны быть осторожны.
Ответ 5
Я нашел интересную записку от Agner на форуме Intel по адресу https://software.intel.com/en-us/forums/intel-isa-extensions/topic/704023
Он отвечает на вопрос о том, что произойдет, если я просто использую ymm8-ymm9, в то время как приложение использует xmm0-xmm7, поэтому мы используем разные регистры.
Вот цитата.
Я только что сделал несколько экспериментов на Хасуэлле. Он обрабатывает весь вектор регистрирует как имеющую грязную верхнюю половину, если только один регистр ymm имеет был затронут. Другими словами, если вы измените ymm1, то не-VEX написание инструкции xmm2 будет иметь ложную зависимость от предыдущее значение xmm2. Рыцари Посадки не имеют такой ложной зависимости. Возможно, он запоминает состояние каждого регистра отдельно?
Надеемся, что будущие процессоры Intel будут либо запоминать состояние каждый регистр отдельно или, по крайней мере, обрабатывать zmm16-zmm31 отдельно, что они не загрязняют xmm0-xmm15. Вы можете что-то сказать о это?
Этот ответ от 12/28/2016 остался без ответа.
Также была интересная информация о VZEROUPPER в блоге Agnger на http://www.agner.org/optimize/blog/read.php?i=761