Почему "4" Тоже Магическое Число
К тому, что семь – число магическое, все вроде бы привыкли. А почему четыре?
А потому, что согласно “__fastcall” calling convention, первые четыре аргумента функции передаются через регистры, а не через stack. То есть вызов функции
int __fastcall fooArg4(int a1, int a2, int a3, int a4);
должен работать *непропорционально* быстрее, чем
int __fastcall fooArg5(int a1, int a2, int a3, int a4, int a5);
А как же тогда быть с утверждением о том, что только первые 2 аргумента передаются через регистры ECX и EDX?
Оставим пока без обсуждения "родной до боли" x86, где современные процессоры делают много интересных трюков (на уровне микро архитектуры) для преодоления проблемы малого количества доступных программе регистров, и архитектуру IA64 “Itanium”, где регистров и так хватает для передачи большого числа аргументов.
Согласно MSDN, число 4 действительно является "магическим" для архитектур x64 и ARM. То есть добавление пятого аргумента функции по идее должно приводить к *нелинейному* увеличению времени вызова.
Но что бы ни написали в документации, часто хочется убедиться и знать наверняка. То есть, попробовать и измерить. Давайте попробуем:
int __fastcall fooArg4(int a1, int a2, int a3, int a4)
{
return a1 + a2 + a3 + a4;
}
int __fastcall fooArg5(int a1, int a2, int a3, int a4, int a5)
{
return a1 + a2 + a3 + a4 + a5;
}
int a1 = 1; int a2 = 2; int a3 = 3; int a4 = 4; int a5 = 5;
extern "C" void ExecuteMainBlock()
{
int sum = fooArg4(a1, a2, a3, a4);
sum += fooArg5(a1, a2, a3, a4, a5);
printf("sum: %d\n", sum);
}
int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
ExecuteMainBlock();
return 0;
}
А теперь внимание, Вопрос: Как точно измерить количество циклов процессора (CPU core cycles), затраченных на выполнение соответствующих вызовов?
Опережая неотвратимую дискуссию о том, как правильно использовать и интерпретировать показания различных таймеров высокого разрешения (High Resolution Timers), позвольте сразу предложить правильный ответ: А НИКАК не измерить!
Потому что измерять скорость исполнения Debug Build с выключенной оптимизацией очевидно не имеет никакого смысла, а измерять Release Build этого примера c полной оптимизацией тоже не имеет смысла, что совсем не так уж очевидно. Почему? А потому, что оптимизирующий кодогенератор выдаст для предложенного примера следующий результат:
|WinMain| PROC
str lr, [sp, #-4]!
ldr r0, [pc, #0xC]
mov r1, #0x19
bl printf
mov r0, #0
ldr pc, [sp], #4
ENDP
Что в переводе с "языка" ARM означает
int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
printf("sum: %d\n", 25);
return 0;
}
Так что же делать? Как нам изменить предложенный пример, чтобы заставить компилятор построить оптимизированный код для вызова интересующих нас функций?
Предложения в студию, please! Если я опять не напортачил с конфигурированием блоггера, то канал комментариев должен быть открыт для всех.
Подсказка: предполагается, что обсуждение этого вопроса должно плавно перейти в обсуждение разных "интересностей" из Link Time Code Generation (LTCG).