Преимущества использования 32-битных регистров / инструкций в x86-64
Иногда gcc использует 32-битный регистр, когда я ожидал, что он будет использовать 64-битный регистр. Например, следующий код C:
unsigned long long
div(unsigned long long a, unsigned long long b){
return a/b;
}
компилируется с опцией -O2 для (не включая некоторые элементы):
div:
movq %rdi, %rax
xorl %edx, %edx
divq %rsi
ret
Для беззнакового деления регистр %rdx
должно быть 0
, Это может быть достигнуто с помощью xorq %rdx, %rdx
, но xorl %edx, %edx
кажется, имеет тот же эффект.
По крайней мере, на моей машине не было никакого увеличения производительности (т.е. ускорения) для xorl
над xorq
,
У меня на самом деле больше, чем один вопрос:
- Почему gcc предпочитает 32-битную версию?
- Почему gcc останавливается на
xorl
и не используетxorw
? - Есть ли машины для которых
xorl
быстрее чемxorq
? - Следует ли всегда отдавать предпочтение 32-битному регистру / операциям, если это возможно, а не 64-битному регистру / операциям?
2 ответа
Почему gcc предпочитает 32-битную версию?
Размер кода: префикс REX не требуется.
Почему gcc останавливается на
xorl
и не используетxorw
?
Запись 16-битного частичного регистра не распространяется на ноль до остальных регистров. Кроме того, xorw
для кодирования требуется префикс размера операнда, поэтому он больше xorl
, (См. Также Почему инструкции x86-64 для 32-битных регистров обнуляют верхнюю часть полного 64-битного регистра? Для исторического фона)
Смотрите также Почему GCC не использует частичные регистры? 32-битные регистры не считаются частичными регистрами, потому что их запись всегда записывает весь 64-битный регистр. (И основная проблема заключается в записи частичных регистров, а не в чтении их после записи во всю ширину.)
Существуют ли машины, для которых xorl быстрее, чем xorq?
Да, Silvermont / KNL распознают только xor
обнуление как идиома обнуления (разрыв зависимости и другие хорошие вещи) с размером 32-битного операнда. Таким образом, хотя размер кода одинаков, xor %r10d, %r10d
намного лучше чем xor %r10, %r10
, (xor
нужен префикс REX для r10
независимо от размера операнда).
На всех процессорах размер кода всегда имеет значение для декодирования и использования I-кэша (кроме случаев, когда .p2align
директива просто сделает больше заполнения, если предыдущий код меньше 1). Нет недостатка в использовании 32-битного размера операнда для обнуления xor (или для неявного расширения нуля вообще вместо explict 2, включая использование AVX vpxor xmm0,xmm0,xmm0
к нулю AVX512 zmm0.)
Большинство команд имеют одинаковую скорость для всех размеров операндов, потому что современные процессоры x86 могут позволить себе транзисторный бюджет для широких ALU. Исключения включают imul r64,r64
медленнее, чем imul r32,r32
на процессорах AMD до Ryzen, и Intel Atom, и 64bit div
значительно медленнее на всех процессорах. AMD пре-рыжен медленнее popcnt r64
, Атом / Сильвермонт имеют медленный shld/shrd r64
против r32
Следует ли всегда отдавать предпочтение 32-битному регистру / операциям, если это возможно, а не 64-битному регистру / операциям?
Да, предпочитайте 32-битные операции по крайней мере из соображений размера кода, но учтите, что использование r8..r15 в любом месте инструкции (включая режим адресации) также потребует префикса REX. Поэтому, если у вас есть некоторые данные, с которыми вы можете использовать 32-битный размер операнда (или указатели на 8/16/32-битные данные), предпочитайте хранить их в младших 8 именованных регистрах (e/rax..), а не в высоких 8 пронумерованных регистров.
Но не тратьте лишние инструкции, чтобы это произошло; сохранение нескольких байтов размера кода обычно является наименее важным фактором. например, просто использовать r8d
вместо сохранения / восстановления rbx
так что вы можете использовать ebx
если вам нужен дополнительный регистр, который не должен быть сохранен при вызове. Использование 32-битного r8d
вместо 64-битного r8
не поможет с размером кода, но это может быть быстрее для некоторых операций на некоторых процессорах (см. выше).
Это также относится к случаям, когда вы заботитесь только о младших 16 битах регистра, но все же может быть более эффективно использовать 32-битное добавление вместо 32-битного.
Смотрите также http://agner.org/optimize/ и вики-тег x86.
Сноска 1: Есть редкие сценарии использования для создания инструкций длиннее, чем необходимо ( Какие методы могут быть использованы для эффективного увеличения длины инструкций в современной x86?)
Для выравнивания более поздней цели ветвления без необходимости NOP.
Настройка внешнего интерфейса конкретной микроархитектуры (т. Е. Оптимизация декодирования путем контроля границ команд). Вставка NOP будет стоить дополнительной полосы пропускания внешнего интерфейса и полностью разрушит всю цель.
Ассемблеры не сделают этого за вас, и выполнение этого вручную требует много времени для повторного выполнения каждый раз, когда вы что-либо меняете (и вам, возможно, придется использовать .byte
директивы для ручного кодирования инструкции).
Сноска 2: Я обнаружил одно исключение из правила, согласно которому неявное расширение нуля, по меньшей мере, столь же дешево, как и более широкая операция: 128-битные загрузки Haswell / Skylake AVX, читаемые 256-битной инструкцией, имеют дополнительный 1c хранилища. задержка пересылки против использования 128-битной инструкции. (Подробности в теме на форуме блога Агнера Фога.)
В 64-битном режиме запись в 32-битный регистр обнуляет верхние 32 бита =>xorl %edx, %edx
нули верхней части rdx
бесплатно".
С другой стороны xor %rdx, %rdx
кодируется дополнительным байтом, потому что для этого требуется префикс REX. При попытке обнулить 64-битный регистр, это чистый выигрыш, чтобы записать его как 32-битный регистр.