cmov
- это операция выбора ALU, которая всегда считывает оба источника до проверки условия . Использование источника памяти не меняет этого. Это не предикатная инструкция ARM, которая действует как NOP, если условие было ложным. cmovz eax, [mem]
также безоговорочно пишет EAX, с расширением нуля в RAX независимо от условия.
Что касается большей части ЦП (планировщик вне очереди и т. Д.), То cmovcc reg, [mem]
обрабатывается точно так же, как adc reg, [mem]
: инструкция ALU с 3 входами и 1 выходом . (adc
пишет флаги, в отличие от cmov
, но не обращайте на это внимания.) Операнд источника с микроплавкой памятью - это отдельный моп, который, как оказалось, является частью той же инструкции x86. Так работают правила ISA.
Так, действительно, более подходящая мнемоника для cmovz
как selectz
Только условные нагрузки x86 (которые не работают с ошибочными адресами, просто потенциально работают медленно):
Нормальные нагрузки, защищенные условными ветвями . Неправильное предсказание ветвлений или другие неправильные предположения, приводящие к ошибочной загрузке, обрабатываются довольно эффективно (возможно, начинается просмотр страницы, но как только неверное предположение идентифицировано, выполнение правильного потока инструкций не должно ждать каких-либо операция с памятью, запущенная умозрительным выполнением).
Если на странице, которую вы не можете прочитать, произошел удар TLB, то больше ничего не произойдет, пока сбойная нагрузка не выйдет на пенсию (известно, что она не является спекулятивной и поэтому фактически принимает исключение #PF
page-fault, неизбежно будет медленным). На некоторых процессорах такая быстрая обработка приводит к атаке Meltdown. >. <См. <a href="http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/" rel="nofollow noreferrer">http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/.
rep lodsd
с RCX = 0 или 1. ( не быстро или эффективно, но ветви микрокода являются особыми и не могут получить выгоду от предсказания ветвления на процессорах Intel. См. Что делает установка REP делать? . Энди Глью упоминает о неправильных прогнозах ветвей микрокода, но я думаю, что они отличаются от обычных промахов ветвей, потому что, кажется, есть фиксированная стоимость.)
AVX2 vpmaskmovd/q
/ AVX1 vmaskmovps/pd
. Неисправности подавляются для элементов, где маска равна 0. Для загрузки маски с маской all-0 даже с юридического адреса требуется помощь микрокода ~ 200 циклов в режиме адресации base + index.) См. section 12.9 УСЛОВНЫЕ УПАКОВКИ И ХРАНЕНИЯ SIMD и таблица C-8 в руководстве по оптимизации Intel. (В Skylake магазинам по недопустимому адресу с маской «все ноль» также требуется помощь.)
Более ранняя версия MMX / SSE2 maskmovdqu
предназначена только для магазина (и имеет подсказку NT). Только аналогичная инструкция AVX с элементами dword / qword (вместо байтов) имеет форму загрузки.
- AVX512 маскирует нагрузки
- AVX2 собирается с некоторыми / всеми очищенными элементами маски.
... и, может быть, другие, которые я забыл. Нормальная загрузка внутри транзакций TSX / RTM: ошибка прерывает транзакцию вместо повышения #PF. Но вы не можете рассчитывать на неверную ошибку индекса, вместо того, чтобы просто читать поддельные данные откуда-то поблизости, так что на самом деле это не условная загрузка. Это также не супер быстро.
Альтернативой может быть cmov
адрес, который вы используете безоговорочно, выбирая, с какого адреса загружать. например если бы у вас было 0
для загрузки откуда-то еще, это бы сработало. Но тогда вам придется рассчитывать индексирование таблицы в регистре, не используя режим адресации, чтобы вы могли cmov
окончательный адрес.
Или просто CMOV индекс и заполнить таблицу нулевыми байтами в конце, чтобы вы могли загрузить с table + 128
.
Или используйте ветку, это, вероятно, хорошо предсказывает для многих случаев. Но, возможно, не для таких языков, как французский, где вы найдете сочетание кодовых точек Unicode с более низким 128 и выше в общем тексте.
Проверка кода
Обратите внимание, что [rel]
работает только тогда, когда в режиме адресации нет регистра (кроме RIP). Относительная RIP-адресация заменяет один из 2 избыточных способов (в 32-битном коде) для кодирования [disp32]
. Он использует более короткое кодирование без SIB, в то время как ModRM + SIB все еще может кодировать абсолютное значение [disp32]
без регистров. (Полезно для адресов, таких как [fs: 16]
для небольших смещений относительно локального хранилища потоков с базами сегментов.)
Если вы просто хотите использовать RIP-относительную адресацию, когда это возможно, используйте default rel
вверху вашего файла . [symbol]
будет относительным RIP, но [symbol + rax]
- нет. К сожалению, NASM и YASM по умолчанию default abs
.
[reg + disp32]
- очень эффективный способ индексации статических данных в позиционно-зависимом коде, просто не обманывайте себя, думая, что они могут быть относительными к RIP. См. 32-разрядные абсолютные адреса, более не разрешенные в x86-64 Linux? .
[rel ascii_flags + EDI]
также странно, потому что вы используете 32-битный регистр в режиме адресации в коде x86-64 . Обычно нет причин тратить префикс размера адреса на усечение адресов до 32-разрядного.
Однако, в этом случае, если ваша таблица находится в младших 32 битах виртуального адресного пространства, а ваша функция arg указана только как 32 бита (поэтому вызывающей стороне разрешено оставлять мусор в верхних 32 RDI), на самом деле это выигрыш - использовать [disp32 + edi]
вместо mov esi,edi
или что-то, что можно расширить до нуля. Если вы делаете это нарочно, обязательно прокомментируйте, почему вы используете 32-битный режим адресации.
Но в этом случае использование cmov
в индексе увеличит с нуля до 64-битного для вас.
Также странно использовать DWORD-загрузку из таблицы байтов. Иногда вы пересекаете границу строки кэша и получаете дополнительную задержку.
@ fuz показал версию с использованием относительного RIP LEA и CMOV в индексе.
В позиционно-зависимом коде, где 32-битные абсолютные адреса в порядке, во что бы то ни стало используйте его для сохранения инструкций . Режимы адресации [disp32]
хуже, чем RIP-относительные (на 1 байт длиннее), но режимы адресации [reg + disp32]
отлично подходят, когда позиционно-зависимый код и 32-битные абсолютные адреса в порядке. (Например, x86-64 Linux, но не OS X, где исполняемый файл всегда отображается за пределами младших 32 бит.) Просто имейте в виду, что это не rel
.
; position-dependent version taking advantage of 32-bit absolute [reg + disp32] addressing
; not usable in shared libraries, only non-PIE executables.
ft_isprint:
mov eax, 128 ; offset of dummy entry for "not ASCII"
cmp edi, eax ; check if ascii
cmovae edi, eax ; replace with 128 if outside 0..127
; cmov also zero-extends EDI into RDI
movzx eax, byte [ascii_flags + rdi] ; load table entry
and al, flag_print ; mask the desired flag
; if the caller is only going to read / test AL anyway, might as well save bytes here
ret
Если какая-либо существующая запись в вашей таблице имеет те же флаги, которые вы хотите использовать для высоких значений , например может быть, запись 0
, которую вы никогда не увидите в строках с неявной длиной, вы все равно можете использовать EAX с нулевым или нулевым значением и хранить в таблицах 128 байт, а не 129.
test r32, imm32
занимает больше байтов кода, чем вам нужно . ~127 = 0xFFFFFF80
подходит для байта с расширенным знаком, но это не кодировка TEST r/m32, sign-extended-imm8
. Однако есть такая кодировка для cmp
, как и для всех других непосредственных инструкций.
Вместо этого вы можете проверить наличие без знака выше 127 с помощью cmp edi, 127
/ cmovbe eax, edi
или cmova edi, eax
. Это экономит 3 байта размера кода. Или мы можем сохранить 4 байта, используя cmp reg,reg
, используя 128
, который мы использовали для индекса таблицы.
Проверка диапазона перед индексацией массива также более интуитивна для большинства людей, чем проверка старших битов.
and al, imm8
составляет всего 2 байта против 3 байтов для and r/m32, sign-extended-imm8
. Это не медленнее на любых процессорах, если вызывающая сторона читает только AL. На процессорах Intel до Sandybridge чтение EAX после ANDing в AL может привести к частичной остановке / замедлению регистрации. Sandybridge не переименовывает частичные регистры для операций чтения-изменения-записи, если я правильно помню, а IvB и более поздние версии вообще не переименовывают парциальные регистры low8.
Вы также можете использовать mov al, [table]
вместо movzx
для сохранения другого байта кода. Более ранний mov eax, 128
уже сломал любую ложную зависимость от старого значения EAX, поэтому у него не должно быть снижения производительности. Но movzx
неплохая идея.
Когда все остальное равно, меньший размер кода почти всегда лучше (для объема кэша команд, а иногда и для упаковки в кэш UOP). Если это потребует дополнительных мопов или введет какие-либо ложные зависимости, то при оптимизации скорости это не будет стоить того.