Ответ 1
Подумайте об умеренном коде -g, который позволит сжать код, а не расширять его, особенно перед циклом. например, xor eax,eax
/cdq
если вам нужны два обнуленных регистра, или mov eax, 1
/lea ecx, [rax+1]
чтобы установить регистры в 1 и 2 всего в 8 байтах вместо 10. См. Установка всех битов в CPU зарегистрируйтесь на 1, чтобы узнать больше об этом, и Советы по игре в гольф в машинном коде x86/x64 для более общих идей. Возможно, вы все еще хотите избежать ложных зависимостей.
Или заполните дополнительное пространство, создавая векторную константу на лету вместо загрузки ее из памяти. (Однако добавление большего давления в uop-кеш может быть хуже для более крупного цикла, содержащего ваш цикл setup + внутренний цикл. Но он позволяет избежать пропусков d-кеша для констант, поэтому у него есть потенциал, чтобы компенсировать выполнение большего числа мопов.)
Если вы еще не использовали их для загрузки "сжатых" констант, pmovsxbd
, movddup
или vpbroadcastd
длиннее, чем movaps
. Загрузка трансляций dword/qword бесплатна (нет ALU, просто загрузка).
Если вы вообще беспокоитесь о выравнивании кода, вы, вероятно, беспокоитесь о том, как он находится в кэше L1I или где находятся границы uop-кэша, так что простого подсчета общего количества мопов уже недостаточно, и несколько дополнительных мопов в блокировка перед тем, о ком вы заботитесь, может не быть проблемой вообще.
Но в некоторых ситуациях вы, возможно, захотите оптимизировать пропускную способность декодирования/использование uop-кэша/общее количество uops для инструкций перед выравниванием блока, который вы хотите выровнять.
Инструкции по заполнению, как вопрос, заданный для:
У Агнера Фога есть целый раздел на эту тему: "10.6 Делать инструкции длиннее ради выравнивания" в его руководстве "Оптимизация подпрограмм на языке ассемблера". (Идеи lea
, push r/m64
и SIB взяты оттуда, и я скопировал предложение/фразу или две, иначе этот ответ - моя собственная работа, либо другие идеи, либо написанные до проверки руководства Агнера.)
Он не был обновлен для текущих процессоров, однако: lea eax, [rbx + dword 0]
имеет больше недостатков, чем раньше, чем против mov eax, ebx
, потому что вы пропускаете нулевую -l интенсивность/отсутствие исполнительного модуля mov
. Если это не на критическом пути, пойти на это, хотя. Простая lea
имеет довольно хорошую пропускную способность, и LEA с большим режимом адресации (и, возможно, даже с некоторыми префиксами сегмента) может быть лучше для пропускной способности декодирования/выполнения, чем mov
+ nop
.
Используйте общую форму вместо краткой (без ModR/M) инструкций, таких как push reg
или mov reg,imm
. Например, используйте 2-байтовый push r/m64
для push rbx
. Или используйте эквивалентную инструкцию, которая длиннее, например, add dst, 1
вместо inc dst
, в тех случаях, когда нет никаких недостатков в inc
так что вы уже использовали inc
.
Используйте SIB-байт. Вы можете заставить NASM сделать это, используя один регистр в качестве индекса, например mov eax, [nosplit rbx*1]
(см. Также), но это вредит задержке использования нагрузки по сравнению с простым кодированием mov eax, [rbx]
с байт SIB. Режимы индексированной адресации имеют и другие недостатки в семействе SnB, такие как процесс отмены -l и не использование порта 7 для хранилищ.
Поэтому лучше всего кодировать base=rbx + disp0/8/32=0
используя ModR/M + SIB без индекса reg. (Кодировка SIB для "без индекса" - это кодировка, которая в противном случае означала бы idx = RSP). Режимы адресации [rsp + x]
требуют SIB (base = RSP - это управляющий код, который означает, что есть SIB), и это все время появляется в сгенерированном коде компилятора -g. Таким образом, есть очень веская причина ожидать, что это будет полностью эффективно для декодирования и выполнения (даже для базовых регистров, отличных от RSP) сейчас и в будущем. Синтаксис NASM не может выразить это, поэтому вам придется кодировать вручную. GNU gas Синтаксис Intel от objdump -d
говорит 8b 04 23 mov eax,DWORD PTR [rbx+riz*1]
для примера Agner Fog 10.20. (riz
- вымышленная нотация индекса с нулем, которая означает, что есть SIB без индекса). Я не проверял, принимает ли ГАЗ это как ввод.
Используйте форму imm32
и/или disp32
для которой требуется только imm8
или disp0/disp32
. Проверка Agner Fog кэша uy Sandybridge (таблица 9.1 руководства по микроархитектору) показывает, что значение имеет фактическое значение немедленного/смещения, а не количество байтов, используемых в кодировке команд. У меня нет никакой информации о тайнике Райзена.
Таким образом, NASM imul eax, [dword 4 + rdi], strict dword 13
(10 байт: код операции + modrm + disp32 + imm32) будет использовать категорию 32small, 32small и принимать 1 запись в кэше uop, в отличие от непосредственного или disp32. на самом деле было более 16 значащих бит. (Тогда потребуется 2 записи, а загрузка из кэша UOP потребует дополнительного цикла.)
Согласно таблице Агнера, 8/16/32 малых всегда эквивалентны для SnB. И режимы адресации с регистром одинаковы, независимо от того, нет ли смещения или он мал, поэтому mov dword [dword 0 + rdi], 123456
принимает 2 записи, точно так же, как mov dword [rdi], 123456789
. Я не понял, что [rdi]
+ full imm32 занял 2 записи, но, видимо, это так и есть на SnB.
Используйте jmp/jcc rel32
вместо rel8
. В идеале старайтесь расширять инструкции в тех местах, которые не требуют более длинных кодировок переходов за пределы области, которую вы расширяете. Пэд после целей прыжка для более ранних прыжков вперед, пэд до целей прыжка для более поздних прыжков назад, если они близки к необходимости в rel32 где-то еще. т.е. старайтесь избегать заполнения между веткой и ее целью, если вы не хотите, чтобы эта ветка все равно использовала rel32.
Возможно, вы a32 mov eax, [abs symbol]
закодировать mov eax, [symbol]
как 6-байтовый a32 mov eax, [abs symbol]
в 64-битном коде, используя префикс ize адреса -S, чтобы использовать 32-битный абсолютный адрес. Но это приводит к задержке префикса изменения длины при декодировании на процессорах Intel. К счастью, ни один из NASM/YASM/gas/clang не делает этот код -S по умолчанию, если вы не указываете 32-битный адрес -S, вместо этого используйте 7-байтовый mov r32, r/m32
с режим абсолютной адресации ModR/M + SIB + disp32 для mov eax, [abs symbol]
.
В 64-битном позиционном -d зависимом коде абсолютная адресация является дешевым способом использования 1 дополнительного байта по сравнению с RIP-относительным. Но обратите внимание, что 32-битный абсолютный + немедленный требует 2 цикла для извлечения из кэша UOP, в отличие от RIP-относительного + imm8/16/32, который занимает всего 1 цикл, даже если он все еще использует 2 записи для инструкции. (например, для mov
-store или cmp
). Таким образом, cmp [abs symbol], 123
медленнее извлекать из кэша UOP, чем cmp [rel symbol], 123
, хотя оба принимают по 2 записи в каждом. Без немедленного, нет никаких дополнительных затрат на
Обратите внимание, что исполняемые файлы PIE разрешают ASLR даже для исполняемого файла и являются стандартными настройками во многих дистрибутивах Linux, поэтому, если вы можете сохранить код PIC без каких-либо недостатков, тогда это предпочтительнее.
Используйте префикс REX, когда он вам не нужен, например, db 0x40
/add eax, ecx
.
Обычно небезопасно добавлять префиксы, такие как rep, которые игнорируются текущими процессорами, потому что они могут означать что-то другое в будущих расширениях ISA.
Повторение одного и того же префикса иногда возможно (однако не с REX). Например, db 0x66, 0x66
/add ax, bx
дает префикс префикса операнда 3 -S, который, я думаю, всегда строго эквивалентен одной копии префикса. До 3 префиксов является пределом для эффективного декодирования на некоторых процессорах. Но это работает, только если у вас есть префикс, который вы можете использовать в первую очередь; Вы обычно не используете 16-битный операнд -S ize и, как правило, не хотите, чтобы 32-битный адрес -S ize (хотя это безопасно для доступа к статическим данным в зависимом от позиции -d коде).
Префикс ds
или ss
в инструкции, обращающейся к памяти, не используется и, вероятно, не вызывает замедления работы каких-либо текущих процессоров. (@prl предложил это в комментариях).
Фактически, микроархив Agner Fog использует префикс ds
для movq [esi+ecx],mm0
в примере 7.1. Организация блоков IFETCH для настройки цикла для PII/PIII (без буфера цикла или кэша UOP), ускоряя его с 3 итераций за такт до 2.
Некоторые процессоры (например, AMD) декодируются медленно, когда инструкции имеют более 3 префиксов. На некоторых процессорах это включает обязательные префиксы в инструкциях SSE2 и особенно в инструкциях SSSE3/SSE4.1. В Сильвермонте даже побег байта 0F считается.
В инструкциях AVX может использоваться 2- или 3-байтовый префикс VEX. Для некоторых инструкций требуется 3-байтовый префикс VEX (2-й источник - x/ymm8-15 или обязательные префиксы для SSSE3 или более поздней версии). Но инструкция, которая могла бы использовать 2-байтовый префикс, всегда может быть закодирована 3-байтовым VEX. NASM или GAS {vex3} vxorps xmm0,xmm0
. Если AVX512 доступен, вы также можете использовать 4-байтовый EVEX.
Используйте 64-битный операнд -S ize для mov
даже когда он вам не нужен, например mov rax, strict dword 1
7-байтовую кодировку extended-imm32 в NASM, которая обычно оптимизирует его до 5- byte mov eax, 1
.
mov eax, 1 ; 5 bytes to encode (B8 imm32)
mov rax, strict dword 1 ; 7 bytes: REX mov r/m64, sign-extended-imm32.
mov rax, strict qword 1 ; 10 bytes to encode (REX B8 imm64). movabs mnemonic for AT&T.
Вы могли бы даже использовать mov reg, 0
вместо xor reg,reg
.
mov r64, imm64
эффективно помещается в кэш mov r64, imm64
когда константа действительно мала (подходит для 32-разрядного расширенного знака.) 1 запись в uop-cache и load-time = 1, так же, как для mov r32, imm32
. Декодирование гигантской инструкции означает, что в 16-байтовом блоке декодирования, вероятно, нет места для 3 других инструкций, которые нужно декодировать в том же цикле, если только они не все 2-байтовые. Возможно, немного удлинить несколько других инструкций лучше, чем иметь одну длинную инструкцию.
Расшифровка штрафов за дополнительные префиксы:
- P5: префиксы препятствуют сопряжению, за исключением адреса/операнда -S, только на PMMX.
- Относительно PIII: всегда есть штраф, если инструкция имеет более одного префикса. Этот штраф обычно составляет один такт на дополнительный префикс. (Руководство по микроарху Agner, конец раздела 6.3)
- Silvermont: это, пожалуй, самое жесткое ограничение на то, какие префиксы вы можете использовать, если вы заботитесь об этом. Декодирование останавливается на более чем 3 префиксах, считая обязательные префиксы + 0F escape-байт. Инструкции SSSE3 и SSE4 уже имеют 3 префикса, поэтому даже REX замедляет их декодирование.
- некоторые AMD: возможно, ограничение с 3 префиксами, не включая escape-байты, и, возможно, не включая обязательные префиксы для инструкций SSE.
... TODO: закончите этот раздел. До этого обратитесь к руководству по микроарху Agner Fog.
После ручного кодирования всегда разбирайте ваш двоичный файл, чтобы убедиться, что вы все правильно поняли. К сожалению, NASM и другие ассемблеры не имеют лучшей поддержки для выбора дешевого заполнения в области команд для достижения заданной границы выравнивания.
Синтаксис ассемблера
NASM имеет некоторый синтаксис переопределения кодировки: {vex3}
и {evex}
, NOSPLIT
и strict byte/dword
, а также принудительное использование disp8/disp32 в режимах адресации. Обратите внимание, что [rdi + byte 0]
не допускается, ключевое слово byte
должно стоять на первом месте. [byte rdi + 0]
разрешено, но я думаю, что это выглядит странно.
nasm -l/dev/stdout -felf64 padding.asm
из nasm -l/dev/stdout -felf64 padding.asm
line addr machine-code bytes source line
num
4 00000000 0F57C0 xorps xmm0,xmm0 ; SSE1 *ps instructions are 1-byte shorter
5 00000003 660FEFC0 pxor xmm0,xmm0
6
7 00000007 C5F058DA vaddps xmm3, xmm1,xmm2
8 0000000B C4E17058DA {vex3} vaddps xmm3, xmm1,xmm2
9 00000010 62F1740858DA {evex} vaddps xmm3, xmm1,xmm2
10
11
12 00000016 FFC0 inc eax
13 00000018 83C001 add eax, 1
14 0000001B 4883C001 add rax, 1
15 0000001F 678D4001 lea eax, [eax+1] ; runs on fewer ports and doesn't set flags
16 00000023 67488D4001 lea rax, [eax+1] ; address-size and REX.W
17 00000028 0501000000 add eax, strict dword 1 ; using the EAX-only encoding with no ModR/M
18 0000002D 81C001000000 db 0x81, 0xC0, 1,0,0,0 ; add eax,0x1 using the ModR/M imm32 encoding
19 00000033 81C101000000 add ecx, strict dword 1 ; non-eax must use the ModR/M encoding
20 00000039 4881C101000000 add rcx, strict qword 1 ; YASM requires strict dword for the immediate, because it still 32b
21 00000040 67488D8001000000 lea rax, [dword eax+1]
22
23
24 00000048 8B07 mov eax, [rdi]
25 0000004A 8B4700 mov eax, [byte 0 + rdi]
26 0000004D 3E8B4700 mov eax, [ds: byte 0 + rdi]
26 ****************** warning: ds segment base generated, but will be ignored in 64-bit mode
27 00000051 8B8700000000 mov eax, [dword 0 + rdi]
28 00000057 8B043D00000000 mov eax, [NOSPLIT dword 0 + rdi*1] ; 1c extra latency on SnB-family for non-simple addressing mode
GAS имеет псевдопрефиксы переопределения кодировки {vex3}
, {evex}
, {disp8}
и {disp32}
Они заменяют уже существующие -d суффиксы .s
, .d8
и .d32
.
У ГАЗА нет переопределения к немедленному размеру, только смещения.
GAS позволяет вам добавить явный префикс ds
, используя ds mov src,dst
gcc -g -c padding.S && objdump -drwC padding.o -S
, с ручным редактированием:
# no CPUs have separate ps vs. pd domains, so there no penalty for mixing ps and pd loads/shuffles
0: 0f 28 07 movaps (%rdi),%xmm0
3: 66 0f 28 07 movapd (%rdi),%xmm0
7: 0f 58 c8 addps %xmm0,%xmm1 # not equivalent for SSE/AVX transitions, but sometimes safe to mix with AVX-128
a: c5 e8 58 d9 vaddps %xmm1,%xmm2, %xmm3 # default {vex2}
e: c4 e1 68 58 d9 {vex3} vaddps %xmm1,%xmm2, %xmm3
13: 62 f1 6c 08 58 d9 {evex} vaddps %xmm1,%xmm2, %xmm3
19: ff c0 inc %eax
1b: 83 c0 01 add $0x1,%eax
1e: 48 83 c0 01 add $0x1,%rax
22: 67 8d 40 01 lea 1(%eax), %eax # runs on fewer ports and doesn't set flags
26: 67 48 8d 40 01 lea 1(%eax), %rax # address-size and REX
# no equivalent for add eax, strict dword 1 # no-ModR/M
.byte 0x81, 0xC0; .long 1 # add eax,0x1 using the ModR/M imm32 encoding
2b: 81 c0 01 00 00 00 add $0x1,%eax # manually encoded
31: 81 c1 d2 04 00 00 add $0x4d2,%ecx # large immediate, can't get GAS to encode this way with $1 other than doing it manually
37: 67 8d 80 01 00 00 00 {disp32} lea 1(%eax), %eax
3e: 67 48 8d 80 01 00 00 00 {disp32} lea 1(%eax), %rax
mov 0(%rdi), %eax # the 0 optimizes away
46: 8b 07 mov (%rdi),%eax
{disp8} mov (%rdi), %eax # adds a disp8 even if you omit the 0
48: 8b 47 00 mov 0x0(%rdi),%eax
{disp8} ds mov (%rdi), %eax # with a DS prefix
4b: 3e 8b 47 00 mov %ds:0x0(%rdi),%eax
{disp32} mov (%rdi), %eax
4f: 8b 87 00 00 00 00 mov 0x0(%rdi),%eax
{disp32} mov 0(,%rdi,1), %eax # 1c extra latency on SnB-family for non-simple addressing mode
55: 8b 04 3d 00 00 00 00 mov 0x0(,%rdi,1),%eax
GAS строго менее мощен, чем NASM, для выражения кодировок, превышающих необходимые.