Выбор между выровненными или невыровненными инструкциями x86 SIMD

Обычно существует два типа инструкций SIMD:

A. Те, которые работают с выровненными адресами памяти, вызовут исключение общей защиты (#GP), если адрес не выровнен по границе размера операнда:

movaps  xmm0, xmmword ptr [rax]
vmovaps ymm0, ymmword ptr [rax]
vmovaps zmm0, zmmword ptr [rax]

Б. И те, которые работают с невыровненными адресами памяти, не вызовут такого исключения:

movups  xmm0, xmmword ptr [rax]
vmovups ymm0, ymmword ptr [rax]
vmovups zmm0, zmmword ptr [rax]

Но мне просто любопытно, зачем мне вообще стрелять себе в ногу и использовать выровненные инструкции из первой группы?


person MikeF    schedule 03.09.2018    source источник
comment
Конечно, на производительность. Доступ к выровненной памяти происходит быстрее, он выполняется за один цикл доступа к памяти и не пропускает / не очищает кеш при каждом доступе. См. stackoverflow.com / questions / 2006216 /   -  person memo    schedule 03.09.2018
comment
Выровненные и невыровненные нагрузки - это исторический артефакт (см. это). Сегодня невыровненная загрузка выполняет то же самое, хотя естественно выровненный операнд имеет то преимущество, что никогда не пересекает строку кэша или страницу.   -  person Margaret Bloom    schedule 03.09.2018
comment
Ответы, связанные с @memo, полны дезинформации и устаревшей информации. Теперь невыровненные операции имеют лишь незначительные штрафы. В любом случае, начиная с Nehalem, имеет значение выравнивание адреса, а не выравнивание инструкции.   -  person harold    schedule 03.09.2018
comment
@harold Спасибо, я думаю, ты узнаешь что-то новое каждый день. Итак, инструкции * movaps являются историческими по соображениям совместимости?   -  person memo    schedule 03.09.2018
comment
@memo в основном да, все еще используется как встроенный assert, выровненный, некоторые компиляторы перестали их использовать   -  person harold    schedule 03.09.2018
comment
@harold - штрафы у Intel довольно малы, но не совсем близки к нулю: нагрузки, пересекающие строки кэша, и хранилища имеют половину пропускной способности (и увеличенная задержка, я думаю, но я забыл, насколько). На AMD штрафы намного более значительны и включают штрафы за несогласованный доступ, которые полностью находятся в пределах одной строки кэша. Однако, как вы упомянули, важно только фактическое выравнивание: обе инструкции работают одинаково для выровненных значений.   -  person BeeOnRope    schedule 04.09.2018
comment
@harold И Microsoft, и Intel подняли это на новый уровень. Начиная с VS2017 и ICC2018, оба компилятора будут генерировать невыровненные ходы даже для целей до Nehalem. MS получила strong отрицательный отзыв по этому поводу, но им уже все равно, так как до Nehalem слишком стар.   -  person Mysticial    schedule 04.09.2018
comment
@MargaretBloom: извините, что воскресил это обсуждение. Что-то только что пришло мне в голову. Знаете ли вы, выполняются ли эти выровненные инструкции SSE атомарно, а не невыровненные? Скажем, инструкции AVX-512 на границе адреса 64 байта.   -  person MikeF    schedule 25.09.2018
comment
Атомарность @MikeF IIRC гарантируется только для естественно выровненной загрузки / хранения, но я считаю, что текущие реализации являются атомарными на уровне строки кеша.   -  person Margaret Bloom    schedule 25.09.2018
comment
@MargaretBloom Спасибо. Это единственная ссылка, которую я могу найти: Intel ® Архитектуры 64 и IA-32, Руководство разработчика программного обеспечения, Том 3 (3A, 3B, 3C и 3D): Руководство по системному программированию, раздел 8.1.1. Хотя насчет SIMD-инструкций несколько спорно. Они пытаются сказать, что даже выровненные инструкции SIMD не являются атомарными?   -  person MikeF    schedule 25.09.2018
comment
@MikeF Возможно. Каждое хранилище в кэше является атомарным, но старые процессоры с узкой шириной шины будут реализовывать хранилище SSE как два / четыре независимых хранилища. Каждое хранилище проталкивается, а затем сбрасывается из буфера хранилища независимо, и если третий сбой из-за отложенного признания недействительности TLB (см. 4.10.4.4), то первое, возможно, уже было сброшено в кэш. Я полагаю, что Intel говорит, что они могут реализовать загрузку / сохранение SIMD как последовательность повторяющихся uOPs загрузки / сохранения. Исправит ли это префикс lock? Не понимаю как. Почему бы вам официально не спросить здесь, на ТАК? Это интересно!   -  person Margaret Bloom    schedule 27.09.2018
comment
@MargaretBloom: Спасибо за объяснение. Попробую не забыть спросить в отдельной ветке. (Сейчас слишком занят.)   -  person MikeF    schedule 29.09.2018


Ответы (2)


  • Невыровненный доступ: можно использовать только movups/vmovups. Те же штрафы, которые обсуждались в случае выровненного доступа (см. Далее), применяются и здесь. Кроме того, доступы, которые пересекают строку кэша или границу виртуальной страницы, всегда влекут за собой штраф для всех процессоров.
  • Aligned access:
    • On Intel Nehalem and later (including Silvermont and later) and AMD Bulldozer and later: After predecoding, they are executed in the same exact way for the same operands. This includes support for move elimination. For the fetch and predecode stages, they consume the same exact resources for the same operands.
    • На pre-Nehalem, Bonnell и pre-Bulldozer: они декодируются в разные uops слияния домена и uops несвязанного домена. movups/vmovups потребляют больше ресурсов (в два раза больше) во внешнем интерфейсе и бэкэнде конвейера. Другими словами, movups/vmovups может быть в два раза медленнее, чем movaps/vmovaps, с точки зрения задержки и / или пропускной способности.

Поэтому, если вас не интересуют старые микроархитектуры, обе они технически эквивалентны. Хотя если вы знаете или ожидаете, что данные будут выровнены, вы должны использовать выровненные инструкции, чтобы гарантировать, что данные действительно выровнены, без необходимости добавлять явные проверки в код.

person Hadi Brais    schedule 03.09.2018
comment
Спасибо. Мне любопытно, но если оба они имеют примерно одинаковую производительность на современных процессорах, почему они не устранили это исключение #GP в инструкциях (v) movaps? Почему бы просто не присвоить им псевдоним. - person MikeF; 03.09.2018
comment
@MikeF Инструкции имеют разные кодировки, и существующие приложения могут требовать одну или обе инструкции. Поэтому для запуска таких приложений необходимо поддерживать обе кодировки. Также выровненные версии реализуют проверки выравнивания в оборудовании, что может устранить необходимость выполнять эти проверки в программном обеспечении для кода, который требует выровненных данных. - person Hadi Brais; 03.09.2018
comment
@MikeF - потому что, как только инструкция определена в ISA в одну сторону, вы не можете полностью изменить ее поведение с помощью простого обновления документа! Исключения являются частью этого поведения. - person BeeOnRope; 04.09.2018
comment
Другим фактором является устранение неоднозначности памяти в Sandybridge (и, возможно, в некоторых более новых арках) Intel Arch Manual: Следующие нагрузки не устраняют неоднозначность. Выполнение этих загрузок приостанавливается до тех пор, пока не станут известны адреса всех предыдущих хранилищ. • Загрузки, которые пересекают 16-байтовую границу. • 32-байтовые загрузки Intel AVX, не выровненные по 32 байтам. . Что могло бы быть существенной разницей, если бы рабочая нагрузка имела смешанные загрузки / хранилища. - person Noah; 21.06.2021
comment
Я проверял, что это не так на Tigerlake, но это также может повлиять на skylake / haswell. Нет никаких заметок о том, когда это было изменено. @PeterCordes - person Noah; 21.06.2021
comment
@Noah: Не уверен, что это идеальное место для этих комментариев; вы можете опубликовать его в качестве ответа на Каков фактический эффект от успешного невыровненного доступа на x86?. (Или, может быть, на странице Как я могу точно измерить скорость невыровненного доступа на x86_64, чтобы обсудить, как на самом деле измерить разницу). Эти вопросы и ответы в основном касаются того факта, что movups не имеет штрафа, когда адрес фактически выравнивается во время выполнения на современных процессорах, но не ранее. - person Peter Cordes; 21.06.2021

Я думаю, есть тонкая разница между использованием _mm_loadu_ps и _mm_load_ps даже на «Intel Nehalem и более поздних версиях (включая Silvermont и более поздние версии) и AMD Bulldozer и более поздних версиях», которая может повлиять на производительность.

Операции, которые сворачивают загрузку и другую операцию, такую ​​как умножение, в одну инструкцию, могут выполняться только с load, а не loadu встроенными функциями, если только вы не компилируете с включенным AVX, чтобы разрешить невыровненные операнды памяти.

Рассмотрим следующий код

#include <x86intrin.h>
__m128 foo(float *x, float *y) {
    __m128 vx = _mm_loadu_ps(x);
    __m128 vy = _mm_loadu_ps(y);
    return vx*vy;
}

Это будет преобразовано в

movups  xmm0, XMMWORD PTR [rdi]
movups  xmm1, XMMWORD PTR [rsi]
mulps   xmm0, xmm1

однако, если используются встроенные функции выровненной нагрузки (_mm_load_ps), они компилируются в

movaps  xmm0, XMMWORD PTR [rdi]
mulps   xmm0, XMMWORD PTR [rsi]

что экономит одну инструкцию. Но если компилятор может использовать загрузки в кодировке VEX, это также только две инструкции для невыровненных данных.

vmovups xmm0, XMMWORD PTR [rsi]
vmulps  xmm0, xmm0, XMMWORD PTR [rdi]

Это обеспечивает согласованный доступ, хотя нет разницы в производительности при использовании инструкций movaps и movups в Intel Nehalem и более поздних версиях, Silvermont и более поздних версиях, или AMD Bulldozer и более поздних версиях.

Но может быть разница в производительности при использовании _mm_loadu_ps и _mm_load_ps встроенных функций при компиляции без включенного AVX, в тех случаях, когда компромисс компилятора не movaps против movups, он находится между movups или сворачивание нагрузки в инструкцию ALU. (Что происходит, когда вектор используется только как вход для одного объекта, иначе компилятор будет использовать mov* загрузку, чтобы получить результат в регистре для повторного использования.)

person Z boson    schedule 18.09.2018
comment
OP спрашивает об инструкциях asm, а не о загрузке встроенных функций. Тем не менее, проголосовали за полезный связанный вопрос. (Инструкции AVX не требуют выравнивания своих операндов памяти, но SSE делает это, поэтому компиляция loadu встроенных функций без AVX может стоить вам дополнительных инструкций, что имеет значение даже для современных процессоров.) - person Peter Cordes; 18.09.2018
comment
@PeterCordes, я осознал свою ошибку еще до вашего комментария и уже исправил ее :-) - person Z boson; 18.09.2018
comment
@PeterCordes - это ваши операции редактирования, которые сворачивают нагрузку, а другая операция, такая как умножение, в одну инструкцию, может выполняться только с помощью load, а не встроенными функциями loadu. точный. Сворачивание может быть выполнено для loadu, если оно закодировано vex. - person Z boson; 18.09.2018
comment
Это правда, обновлено, если вы думаете, что здесь тоже лучше упомянуть AVX. В вашем оригинале было такое же упрощение: сначала говорилось только о SSE, я подумал, что вы к этому стремились. - person Peter Cordes; 18.09.2018
comment
@PeterCordes Я в основном хотел указать, что некоторые люди могут сделать неправильный вывод о карте 1-1 между встроенными функциями и инструкциями, которые могут иметь влияние (например, просто используйте loadups, потому что movups и movaps не имеют значения - это не обязательно поправлю). - person Z boson; 18.09.2018
comment
Да, поэтому я проголосовал за. Я думаю, что этот ответ делает это сейчас хорошо. - person Peter Cordes; 18.09.2018
comment
Так что, ребята, извините, позвольте мне уточнить. Причина, по которой он скомпилирован в 3 инструкции для невыровненной памяти, заключается в том, что mulps поддерживает только выровненный операнд памяти, правильно? - person MikeF; 20.09.2018
comment
Операции чтения-изменения @MikeF (например, mul + read) требуют выровненной памяти с SSE, но не с AVX. - person Z boson; 20.09.2018
comment
@PeterCordes, меня что-то беспокоит по поводу изменения моего ответа на внутреннюю функцию. OP может действительно захотеть узнать об операциях чтения в целом, а не только о конкретных инструкциях по чтению. На более базовом уровне выровненные и невыровненные микрооперации не имеют разницы в производительности для выровненной памяти. Однако есть разница в том, как эти микрооперации используются либо для однократного чтения, либо для чтения с изменением. Проблема в том, что люди обычно думают только о чистом чтении, а не о модификационном чтении. И я думаю, что ОП, вероятно, хотел знать об обоих. - person Z boson; 20.09.2018
comment
Если вы напишете movaps в asm, ничто не сможет поместить его в операнд памяти. Это может произойти только во время компиляции с load встроенным, но не во время сборки или выполнения с movaps. movaps xmm0, [rdi] / addps xmm1, xmm0 не не слиты во время выполнения. Вот почему в этом ответе необходимо использовать встроенные функции, а не мнемонику asm, чтобы правильно объяснить, как компилятор может оптимизировать. Или с блоками вывода asm, чтобы показать, как оптимизация полностью удаляет инструкцию movaps, заменяя ее инструкцией, которая будет декодироваться в микросхему (Intel) или просто источник памяти (AMD). - person Peter Cordes; 20.09.2018
comment
Или вы имели в виду, что хотите подробнее рассказать о микрослиянии, чтобы показать, как на процессорах семейства Intel SnB инструкция с операндом памяти принимает 2 записи в RS, чтобы удерживать свои 2 мупа с несвязанными доменами? (Забавный факт: микросхема нагрузки + ALU uop в семействе P6 умещается в одной записи RS, даже несмотря на то, что их все еще нужно отправлять на отдельные порты выполнения. Понимание влияния lfence на цикл с двумя длинными цепочками зависимостей для увеличения длины) - person Peter Cordes; 20.09.2018
comment
@PeterCordes, я просто имел в виду, что существует как минимум два вида инструкций чтения SIMD: только чтение и изменение + чтение. А для SSE существует асимметрия между выровненным и невыровненным, так что возможны только выровненные модификации + чтения. ОП спрашивал только об инструкциях чтения, но, возможно, также хотел узнать об инструкциях чтения + изменения. - person Z boson; 20.09.2018
comment
О, да, я согласен, это часто повторяемый ответ, что Nehalem имеет эффективные невыровненные нагрузки, а затем неправильно экстраполирует от movups эффективности к _mm_loadu_ps эффективности. Все мои правки были направлены на то, чтобы более прояснить этот момент. Побеждает именно микрослияние (или поведение AMD, требующее только 1 m-op / uop даже с mem src). Что касается uop, addps xmm1, [rdi] декодирует на Intel тот же тип uop, что и movaps load, микроплавлен с тем же типом uop, что и addps xmm0,xmm1, так что вы можете добавить это, но ваша первая версия казалась запутанной относительно isns и intrin - person Peter Cordes; 20.09.2018