x86 - параллелизм на уровне инструкций - оптимальный порядок инструкций

Какой из следующих двух фрагментов кода x86_64 должен быть самым быстрым? Или вообще никакой разницы?

; #1
    bsf    rax, rdi
    mov    rdx, -1
    cmove  rax, rdx

против

; #2
    mov    rdx, -1
    bsf    rax, rdi
    cmove  rax, rdx

(Или альтернатива №1, более экономичная с регистрами.

; #1a
    bsf    rax, rdi
    mov    rdi, -1
    cmove  rax, rdi

)

И да, я знаю, что я должен просто сравнить их, но у меня нет инструментов, и из-за текущей длительной болезни, связанной с инвалидностью, я не могу сейчас все настроить.

1 ответ

Решение

См. Также ссылки на производительность в вики-теге x86, особенно в pdf-файле Agner Fog и его Руководстве по оптимизации сборки.


Если только эффекты декодирования / внешнего интерфейса не вступают в игру, все они в основном равны из-за неупорядоченного исполнения. (В противном случае это зависит от окружающего кода и отличается для разных микроархитектур.)

Все они имеют одинаковое количество параллелизма (две цепочки: независимые mov (без входов) и bsf (один вход), плюс зависимый cmov). Он достаточно мал, чтобы выполнить параллельное выполнение тривиально. Если вам небезразличен Atom по порядку, то в любом случае bsf и mov могут спариться.

Любая разница будет зависеть от окружающего кода.

Если бы мне пришлось выбирать, я мог бы выбрать #1aпотому что это уменьшает вероятность mov красть порт исполнения из bsf, mov r64, imm32-sign-extended может работать на любом порту на большинстве процессоров, но bsf обычно не может Размещение инструкций на критическом пути перед insns, которые не уменьшают конфликты ресурсов, по крайней мере вне циклов, где некритические инструкции из предыдущей итерации могут задерживать критический путь. (The mov Это своего рода критический путь, но он не имеет входных задержек, поэтому при неправильном выполнении он может быть запущен в любой момент после его выдачи, возможно, до инструкций, которые производят bsfвход.)

Я бы наверное использовал #1a над #1 чтобы сделать этот фрагмент использовать меньше регистров для будущего. Я бы использовал #1 если бы у меня было определенное использование для запуска новой цепочки зависимостей для некоторого регистра, например, если в более поздней инструкции была ложная зависимость, а значение регистра зависело от длинной цепочки зависимостей (или нагрузки, которая могла бы пропустить кеш). например, если я хотел использовать 8-битный или 16-битный регистр или выходной регистр дляpopcnt,

Говоря о которых, bsf вероятно, также имеет ложную зависимость от процессоров Intel. Если входное значение равно 0, процессоры Intel оставляют место назначения без изменений. (ISA говорит, что dest не определено, но это то, что фактически делает Core2, например. Это требует зависимости как от регистра назначения, так и от источника). Я подозреваю, что именно поэтому lzcnt / tzcnt / popcnt иметь зависимость от выходного регистра.

Говоря о ложных зависимостях: забавный факт, вы можете установить регистр для всех с меньшим количеством байтов машинного кода, выполнив or rdx, -1 (or r64, imm8), с ложной зависимостью от регистра dst., Обычно плохая идея, не делай этого.

Другие вопросы по тегам