Как избежать штрафов за переход 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-разрядную версию с почти таким же кодом. Штраф возникает только тогда, когда ЦП фактически пытается использовать регистр xmm, который раньше использовался как ymm и имеет один из старших 128 битов, отличный от нуля? Не лучше ли просто обнулить регистры ymm, которые я использовал после быстрого копирования в память? Например, я использовал регистр ymm один раз для копирования 32 байтов памяти - какой самый быстрый способ обнулить его? Достаточно ли быстро "vpxor ymm15, ymm15, ymm15"? (AFAIK, vpxor может выполняться на любом из 3 портов исполнения ALU, p0/p1/p5, тогда как vxorpd может выполняться только на p5). Не будет ли время обнулить его больше, чем выигрыш от его использования для копирования 32 байтов памяти?
4 ответа
Чтобы избежать штрафов на всех архитектурах просто нужно оформить vzeroall
или же vzeroupper
после части вашего кода, которая использует инструкции, закодированные в VEX, до возврата к остальной части кода, которая использует инструкцию, не относящуюся к VEX.
Выпуск этих инструкций в любом случае считается хорошей практикой для всех подпрограмм, использующих AVX, и стоит дешево - за исключением, возможно, в Knights Landing, но я сомневаюсь, что вы используете эту архитектуру. Даже если это так, характеристики производительности сильно отличаются от семейства десктопов /Xeon, так что, возможно, вам все равно понадобится отдельная компиляция.
Это единственные инструкции, которые перемещаются из грязного верхнего в чистое верхнее состояние. Вы не можете просто обнулить определенные регистры, которые вы использовали, поскольку микросхема не отслеживает грязное состояние на основе регистра за регистром.
Стоимость этих vzero*
Инструкции - это несколько циклов: так что, если то, что вы делаете в AVX, того стоит, оно, как правило, стоит того, чтобы заплатить эту небольшую плату.
Другая возможность - использовать регистры zmm16 - zmm31. Эти регистры не имеют аналогов, отличных от VEX. Нет перехода состояния и нет штрафа за смешивание zmm16 - zmm31 с кодом не-VEX SSE. Эти 512-битные регистры доступны только в 64-битном режиме и только на процессорах с AVX512.
Оптимальным решением, вероятно, является перекомпиляция всего кода с префиксами VEX. Инструкции, закодированные в 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 является то, что код несовместим со старыми процессорами, поэтому вам может потребоваться сохранить старую версию для работы на старых процессорах
По моему опыту лучший способ Avoiding AVX-SSE (VEX) Transition Penalties
позволить компилятору использовать нативный код микроархитектуры. Например, вы можете использовать SSE-Intrinsics
рядом с AVX-Intrinsics
и использовать -march=native
, мой GCC 6.2
компилирует программу и использует VEX-Encoded
инструкции. Если вы увидите созданную сборку, вы найдете дополнительную v
до всех SSE переведенных кодов. С другой стороны, если вы сомневаетесь, вы можете использовать __asm__ __volatile__ ( "vzeroupper" : : : );
каждая точка вашей программы, после использования ymm
регистрируется, но вы должны быть осторожны с этим.
Я нашел интересную заметку Агнера на форуме Intel по адресу https://software.intel.com/en-us/forums/intel-isa-extensions/topic/704023
Он отвечает на вопрос о том, что произойдет, если я просто использую ymm8-ymm9, в то время как приложение использует xmm0-xmm7, поэтому мы используем разные регистры.
Вот цитата.
Я только что провел еще несколько экспериментов на Haswell. Он обрабатывает все векторные регистры как имеющие грязную верхнюю половину, если был затронут только один регистр ymm. Другими словами, если вы измените ymm1, то запись без VEX инструкции, записывающей в xmm2, будет иметь ложную зависимость от предыдущего значения xmm2. У Knights Landing такой ложной зависимости нет. Возможно, он запоминает состояние каждого регистра отдельно?
Будем надеяться, что будущие процессоры Intel будут либо помнить состояние каждого регистра отдельно, либо, по крайней мере, обрабатывать zmm16-zmm31 отдельно, чтобы они не загрязняли xmm0-xmm15. Можете ли вы рассказать что-нибудь об этом?
Этот ответ от 28.12.2016 оставлен без ответа.
Также была интересная информация о VZEROUPPER в блоге Аггнера по адресу http://www.agner.org/optimize/blog/read.php?i=761