2015-10-26 7 views
1

Я читал о функциях сборки, и я смущен относительно того, следует ли использовать ввод и выход или просто инструкции вызова/возврата для быстрого выполнения. Является ли один способ быстрым, а другой меньше? Например, что является самым быстрым (STDCALL) способом сделать это в сборке без встраивания функции:Самый быстрый (процессорный) способ выполнения функций в сборке Intel x64?

static Int32 Add(Int32 a, Int32 b) { 
    return a + b; 
} 

int main() { 
    Int32 i = Add(1, 3); 
} 

ответ

5

Использование call/ret, не делая кадр стека либо enter/leave или push&pop rbp/mov rbp, rsp. gcc (со значением по умолчанию) создает только стек стека в функциях, которые выполняют распределение по размеру в стеке. This may make debugging slightly more difficult, так как gcc обычно испускает информацию об удалении стека при компиляции с , но у вашего рукописного asm этого не будет. Обычно имеет смысл писать листовые функции в asm или, по крайней мере, те, которые не называют многими другими функциями.

Кадры стека означают, что вам не нужно отслеживать, насколько указатель стека изменился с момента ввода функции для доступа к материалам в стеке (например, функции args и spill slots для местных жителей). Оба Windows и Linux/Unix 64-битные ABI передают первые несколько аргументов в регистрах, и часто бывает достаточно регр, которые вам не нужно разливать в стек. В большинстве случаев стоп-кадры являются пустой тратой инструкций. В 32-битном коде, имеющем ebp (от 6 до 7 GP regs, не считая указателя стека), большая разница, чем от 14 до 15. Конечно, вам все равно нужно push/pop rbp, если вы используете do, хотя, поскольку в обоих ABI это зарегистрированный пользователем реестр, функции не допускаются к clobber.

Если вы оптимизируете x86-64 asm, вы должны прочитать Agner Fog's guides, а также проверить некоторые другие ссылки в вики-файле .

Лучшая реализация вашей функции, вероятно:

align 16 
global Add 
Add: 
    lea eax, [rdi + rsi] 
    ret 
    ; the high 32 of either reg doesn't affect the low32 of the result 
    ; so we don't need to zero-extend or use a 32bit address-size prefix 
    ; like lea eax, [edi, esi] 
    ; even if we're called with non-zeroed upper32 in rdi/rsi. 

align 16 
global main 
main: 
    mov edi, 1 ; 1st arg in SysV ABI 
    mov esi, 3 ; 2nd arg in SysV ABI 
    call Add 
    ; return value in eax in all ABIs 
    ret 

align 16 
OPmain: ; This is what you get if you don't return anything from main to use the result of Add 
    xor eax, eax 
    ret 

Это на самом деле what gcc emits для Add(), но она по-прежнему оказывается основной в пустой функции, или в return 4, если вы return i. clang 3.7 уважает -fno-inline-functions, даже если результатом является константа времени компиляции. Это превосходит мой asm, делая оптимизацию хвостового вызова, и jmp ing до Add.

Обратите внимание, что Windows 64bit ABI использует разные регистры для функций args. См. Ссылки в вики-теге x86 или руководстве ABI Agner Fog. Assembler macros может помочь для написания функций в asm, которые используют правильные регистры для своих аргументов, в зависимости от платформы, на которую вы нацеливаете.

+0

Я думаю, что функция 'main' неверна. Для другой функции возвращение без явного 'return' будет ошибкой,' main' является специальной. Неявный возврат из 'main' эквивалентен возврату значения' 0'. Здесь вы возвращаете всякий мусор, найденный в 'eax', а именно 4, которого вы не должны. –

+0

@JensGustedt: Мой asm соответствует моему модифицированному C, который возвращает 'i', чтобы оптимизировать компиляторы, чтобы по-прежнему вызывать' Add' (при использовании '-fno-inline-functions'). Основной пункт OP просто компилируется в 'xor eax, eax/ret', как вы правильно указываете. –