Почему мы используем регистр sub esp, 4 вместо pu sh a в сборке? - PullRequest
2 голосов
/ 18 июня 2020

Если мы используем

push ecx

, мы должны использовать один байт в коде операции, если мы используем

sub esp, 4 

Я думаю, мы должны использовать 2 байта? Я пытался прочитать документацию , но мало что понял. Причина та же, что и в

xor eax, eax 

вместо

mov eax, 0

1 Ответ

2 голосов
/ 18 июня 2020

TL: DR: Clang уже делает это. G CC не за исключением -Os. Я не тестировал.


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

Исторически (16-битная x86 до того, как у ЦП были кеши), push cx, вероятно, не был бы быстрее, чем sub sp, 2 ( 3 байта) или dec sp / dec sp (2 байта) на тех старых процессорах, где пропускная способность памяти была основным фактором производительности (включая выборку кода). Оптимизация скорости, особенно на 8088, примерно такая же, как оптимизация для размера кода.

Причина, по которой xor eax,eax по-прежнему предпочтительнее, заключается в том, что более поздние процессоры смогли сделать его по крайней мере таким же быстрым даже без кода - преимущество в размере. Как лучше всего обнулить регистр в сборке x86: xor, mov или and?


На более поздних процессорах, таких как PPro, push декодируется в несколько мопов (настраивать ESP и хранить отдельно). Таким образом, на этих процессорах, несмотря на меньший размер кода, он стоит дороже во внешнем интерфейсе. Или на P5 Pentium (который не декодировал сложные инструкции в несколько мопов), push временно останавливал конвейер, и компиляторы часто избегали этого, даже когда был желателен побочный эффект сохранения в памяти.

Но наконец, около Pentium-M, процессоры получили «стековой движок» , который обрабатывает часть операций обновления стека ESP за пределами неупорядоченной серверной части, что делает его однократным и с нулевой задержкой (для цепи депозита через ESP). Как видно из этой ссылки, команда stack-syn c, которую механизм стека должен вставлять, иногда действительно делает sub esp,4 стоимостью более push, если вы еще не собирались ссылаться на esp непосредственно в задний конец перед следующей операцией стека. (например, call)

IDK, если действительно было бы хорошей идеей начать использовать фиктивный push ecx на старых процессорах, или если ограниченные размеры буфера хранилища означали, что это плохая идея использовать ресурсы выполнения для создания фиктивных хранилищ, даже для кэширования строк, которые почти наверняка были горячими (верх стека).

Но в любом случае современные компиляторы делают используют это оптимизация глазка, , особенно в 64-битном режиме, где обычно требуется настроить стек только на один пу sh. Современные процессоры имеют большие буферы хранения.

void foo();

int bar() {
    foo();
    return 0;
}

Clang делает это уже несколько лет. например, с текущим лязгом 10,0 -O3 (оптимизация для скорости по размеру) на Godbolt

bar():
        push    rax
        call    foo()
        xor     eax, eax
        pop     rcx
        ret

G CC делает это на -Os, но не на -O3 (I пробовал с -march=skylake, по-прежнему предпочитает использовать sub.)

Труднее построить случай, когда sub esp,4 будет полезным, но это работает:

int bar() {
    volatile int arr[1]= {0};
    return 0;
}

clang10 .0 -m32 -O3 -mtune = skylake

bar():                                # @bar()
        push    eax
        mov     dword ptr [esp], 0     # missed optimization for push 0
        xor     eax, eax
        pop     ecx
        ret

К сожалению, компилятор не замечает того факта, что push 0 мог иметь как инициализированное, так и зарезервированное пространство для объекта volatile int, заменяя оба push eax и mov dword [esp], 0 Какой компилятор C / C ++ может использовать инструкции pu sh pop для создания локальных переменных вместо простого увеличения esp один раз?

...