Ответ 1
Короткий ответ: Опуская указатель кадра,
Вам нужно использовать указатель стека для доступа к локальным переменным и аргументам. Компилятор не возражает, но если вы кодируете сообщение, это делает вашу жизнь немного сложнее. Намного сложнее, если вы не используете макросы.
Вы сохраняете четыре байта (32-разрядную архитектуру) пространства стека за вызов функции. Если вы не используете глубокую рекурсию, это не победа.
Вы сохраняете запись в память в кэше (стек), и вы (теоретически) сохраняете несколько тиков часов при входе/выходе функции, но вы можете увеличить размер кода. Если ваша функция делает очень мало очень часто (в этом случае она должна быть встроена), это не должно быть примечательным.
Вы освобождаете регистр общего назначения. Если компилятор может использовать регистр, он будет генерировать код, который существенно меньше и потенциально быстрее. Но, если большая часть времени процессора расходуется, разговаривая с основной памятью (или даже с жестким диском), отсутствие указателя на фрейм не исчезает из этого.
Отладчик потеряет простой способ генерации трассировки стека. Отладчик может по-прежнему иметь возможность генерировать трассировку стека из другого источника (например, PDB файл).
Длинный ответ:
Типичный вход и выход функции:
PUSH SP ;push the frame pointer
MOV FP,SP ;store the stack pointer in the frame pointer
SUB SP,xx ;allocate space for local variables et al.
...
LEAVE ;restore the stack pointer and pop the old frame pointer
RET ;return from the function
Запись и выход без указателя стека могут выглядеть так:
SUB SP,xx ;allocate space for local variables et al.
...
ADD SP,xx ;de-allocate space for local variables et al.
RET ;return from the function.
Вы сохраните две инструкции, но вы также продублируете буквенное значение, чтобы код не становился короче (совсем наоборот), но вы могли бы сохранить несколько тактов (или нет, если это приведет к промаху в кеше в кэш команд). Однако вы сохранили некоторое пространство в стеке.
Вы освобождаете регистр общего назначения. Это имеет только преимущества.
В regcall/fastcall это один дополнительный регистр, в котором вы можете хранить аргументы в своей функции. Таким образом, если ваша функция занимает семь (на x86; больше на большинстве других архитектур) или больше аргументов (включая this
), седьмой аргумент по-прежнему вписывается в регистр. То же самое, что более важно, относится и к локальным переменным. Массивы и большие объекты не помещаются в регистры (но указатели на них), но если ваша функция использует семь разных локальных переменных (включая временные переменные, необходимые для вычисления сложных выражений), скорее всего, компилятор сможет создать меньший код, Меньший код означает более низкий размер кэша команд, что означает снижение частоты пропусков и, следовательно, еще меньший доступ к памяти (но Intel Atom имеет кэш-память 32K, что означает что ваш код, вероятно, подойдет в любом случае).
Архитектура x86 поддерживает режимы адресации [BX/BP/SI/DI]
и [BX/BP + SI/DI]
. Это делает регистр ВР чрезвычайно полезным местом для масштабированного индекса массива, особенно если указатель массива находится в регистрах SI или DI. Два регистра смещения лучше, чем один.
Использование регистра позволяет избежать доступа к памяти, но если переменная стоит хранить в регистре, скорее всего, она сохранится так же хорошо, как в кеше L1 (тем более, что она будет находиться в стеке). По-прежнему стоимость перехода в/из кеша, но поскольку современные процессоры делают многое движение оптимизации и параллелизации, возможно, что доступ L1 будет таким же быстрым, как доступ к регистру. Таким образом, скорость выгоды от не перемещение данных вокруг все еще присутствует, но не так огромна. Я легко могу представить себе, как CPU полностью отказывается от кэша данных, по крайней мере, до чтения (и запись в кеш может выполняться параллельно).
Регистр, который используется, является регистром, который нуждается в сохранении. В регистрах не стоит много хранить, если вы собираетесь в любой момент нажать его в стек, прежде чем использовать его снова. В соглашениях о вызове с помощью вызывающего абонента (например, выше) это означает, что регистры как постоянное хранилище не так полезны в функции, которая вызывает другие функции.
Также обратите внимание, что x86 имеет отдельное пространство регистров для регистров с плавающей запятой, что означает, что поплавки не могут использовать регистр BP без дополнительных команд перемещения данных. Только целые числа и указатели памяти.
То, что вы теряете, опуская указатели на кадры, является отладочной. Этот ответ показывает, почему:
Если код сработает, все отладчику необходимо выполнить для создания трассировки стека:
PUSH FP ; log the current frame pointer as well
$1: CALL log_FP ; log the frame pointer currently on stack
LEAVE ; pop the frame pointer to get the next one
CMP [FP+4],0
JNZ $1 ; until the stack cannot be popped (the return address is some specific value)
Если код выходит из строя без указателя кадра, отладчик может не иметь возможности генерировать трассировку стека, потому что он может не знать (а именно, ему нужно найти точку входа/выхода функции), сколько нужно вычесть из указатель стека. Если отладчик не знает, что указатель кадра не используется, он может даже сбой себя.