Добавить в корзинуПозвонить
Найти в Дзене

Оптимизация вызова функции

На нескольких практических примерах посмотрим, как выглядит оптимизированный код, сгенерированный компилятором с опцией -O 3. Удаление функции Давайте посмотрим на простую программу, которая будет получать пароль с клавиатуры и вычислять контрольную сумму. #include once "crt.bi"
#include once "Hash.bi"
Const PASSWORD_CAPACITY = 32
Public Function VerifyPassword(ByVal Hash As UInteger) As Boolean
' Пароль
Dim Password As ZString * (PASSWORD_CAPACITY + 1) = Any
' Получаем пароль с консоли
fgets(@Password, PASSWORD_CAPACITY, stdin)
' Вычисляем контрольную сумму
Dim LocalHash As UInteger = CalculateHash(@Password)
' Обнуляем память перед выходом из функции
' чтобы злоумышленник не смог прочитать пароль
memset(@Password, 0, PASSWORD_CAPACITY * SizeOf(ZString))
' Проверить совпадение контрольных сумм
If Hash = LocalHash Then
Return True
End If
Return False
End Function Здесь нет ничего сложного. Функция fgets получает строку с консоли (пароль), затем CalculateHas
Оглавление

На нескольких практических примерах посмотрим, как выглядит оптимизированный код, сгенерированный компилятором с опцией -O 3.

Удаление функции

Давайте посмотрим на простую программу, которая будет получать пароль с клавиатуры и вычислять контрольную сумму.

#include once "crt.bi"
#include once "Hash.bi"

Const PASSWORD_CAPACITY = 32

Public Function VerifyPassword(ByVal Hash As UInteger) As Boolean

' Пароль
Dim Password As ZString * (PASSWORD_CAPACITY + 1) = Any

' Получаем пароль с консоли
fgets(@Password, PASSWORD_CAPACITY, stdin)

' Вычисляем контрольную сумму
Dim LocalHash As UInteger = CalculateHash(@Password)

' Обнуляем память перед выходом из функции
' чтобы злоумышленник не смог прочитать пароль
memset(@Password, 0, PASSWORD_CAPACITY * SizeOf(ZString))

' Проверить совпадение контрольных сумм
If Hash = LocalHash Then
Return True
End If

Return False

End Function

Здесь нет ничего сложного. Функция fgets получает строку с консоли (пароль), затем CalculateHash вычисляет контрольную сумму. После чего контрольная сумма проверяется на совпадение с искомой, если совпадение обнаружено, то возвращается истина.

Здесь также есть вызов функции memset, которая обнуляет пароль пользователя, чтобы его не смог прочитать злоумышленник.

Теперь посмотрим ассемблерный код оптимизированного варианта программы:

VERIFYPASSWORD:
push rbp # пролог функции
mov rbp, rsp
push rsi
push rbx
mov rbx, rcx
and rsp, -16 # выравнивание стека
sub rsp, 80 # создание локальных переменных
call __iob_func # получение дескрипторов ввода‐вывода
lea rsi, 32[rsp]
mov edx, 32
mov rcx, rsi
mov r8, rax
call fgets # получение строки с консоли
mov rcx, rsi
call CALCULATEHASH # вычисление контрольной суммы
cmp rax, rbx # сравнение
sete al
lea rsp, -16[rbp] # восстановление стека
pop rbx # восстановление регистров
pop rsi # эпилог функции
pop rbp
ret

Как видно, вызова функции memset не осталось. Компилятор выбросил вызов memset, это не ошибка, а законные действия оптимизатора: нет смысла что‐то делать с буфером (обнулять), который затем не используется. С точки зрения языка, программы с memset и без memset одинаковы, поэтому удаление memset не повлияет на поведение программы.

Замена функции константой

Напишем программу вычисления факториала. В качестве образца возьмём число 5:

Private Function FactorialRecursive(ByVal n As UInteger, ByVal Accumulator As UInteger) As UInteger

If n Then
Return FactorialRecursive(n - 1, Accumulator * n)
End If

Return Accumulator

End Function

Private Function Factorial(ByVal n As UInteger) As UInteger

Return FactorialRecursive(n, 1)

End Function

Dim f As UInteger = Factorial(5)
Print f

Функция использует хвостовую рекурсию. Это значит, что возврат значения функции происходит сразу, без дополнительных операций с результатом. Посмотрим на машинный код, сгенерированный оптимизатором:

main:
push r13
push r12
sub rsp, 40
mov r12d, ecx
mov r13, rdx
call __main # Предварительные подготовления
xor r8d, r8d
mov rdx, r13
mov ecx, r12d
call fb_Init # Инициализации
mov r8d, 1
mov edx, 120 # Вычисленное значение
xor ecx, ecx
call fb_PrintULongint # Вывод числа на консоль
xor ecx, ecx
call fb_End # Выход
xor eax, eax
add rsp, 40
pop r12
pop r13
ret

Здесь оптимизатор сам вычислил факториал числа 5 и подставил значение 120 в код.

Выводы

Используйте оптимизатора для получения быстрого кода.