enter
работает необычно медленно на всех процессорах, никто не использует его, за исключением, возможно, оптимизации размера кода за счет скорости. (Если указатель кадра вообще необходим или желательно обеспечить более компактные режимы адресации для адресации пространства стека.)
leave
достаточно быстр, чтобы его стоило использовать, а GCC действительно использует его (если ESP / RSP еще не указывает на сохраненный EBP / RBP; в противном случае используется просто pop ebp
).
leave
составляет всего 3 мупа на современных процессорах Intel (и 2 на некоторых AMD). (https://agner.org/optimize/, https://упс.info/).
mov / pop всего 2 мупа (на современной x86, где движок стека отслеживает обновления ESP / RSP). Так что leave
- это всего лишь на один удар больше, чем делать что-то по отдельности. Я тестировал это на Skylake, сравнивая call / ret в цикле с функцией, устанавливающей традиционный указатель фрейма и разрывая его фрейм стека с помощью _6 _ / _ 7_ или leave
. perf
счетчики для uops_issued.any
показывают на один интерфейсный uop больше, чем для mov / pop. (Я провел свой собственный тест на случай, если другие методы измерения подсчитывают синхронизацию стека в своих измерениях отпуска, но используют его в реальных элементах управления для этого.)
Возможные причины, по которым старые процессоры могли получить больше преимуществ при разделении mov / pop:
В большинстве процессоров без кэша uop (например, Intel до Sandybridge, AMD до Zen) инструкции с несколькими uop могут быть узким местом при декодировании. Они могут декодировать только в первом (сложном) декодере, что может означать, что цикл декодирования до этого производил меньше мопов, чем обычно.
Некоторые соглашения о вызовах Windows представляют собой аргументы стека вызываемых объектов с использованием ret n
. (например, ret 8
для выполнения ESP / RSP + = 8 после появления адреса возврата). Это многопозиционная инструкция, в отличие от простой около ret
на современных x86. Таким образом, указанная выше причина двоякая: оставьте и ret 12
не смогли декодировать в одном и том же цикле.
Эти причины также применимы к устаревшему декодированию для создания записей uop-cache.
P5 Pentium также предпочел RISC-подобную подмножество x86, будучи неспособным даже разбить сложные инструкции на отдельные uops вообще.
Для современных ЦП leave
занимает 1 лишнюю моп в кэше мопов. И все 3 должны находиться в одной строке кэша uop, что может привести только к частичному заполнению предыдущей строки. Таким образом, больший размер кода x86 может фактически улучшить упаковку в кэш uop. Или нет, смотря как обстоят дела.
Сохранение 2 байтов (или 3 в 64-битном режиме) может стоить или не стоить 1 лишних моп на функцию.
GCC поддерживает leave
, clang и MSVC поддерживает _17 _ / _ 18_ (даже с clang -Oz
оптимизацией размера кода даже за счет скорости, например, выполнение таких вещей, как push 1 / pop rax
(3 байта) вместо 5-байтового mov eax,1
).
ICC отдает предпочтение mov / pop, но с -Os
будет использовать leave
. https://godbolt.org/z/95EnP3G1f
person
Peter Cordes
schedule
06.05.2021