Могут ли компиляторы C оптимизировать адреса в встроенных функциях?
Допустим, у меня есть следующий код:
int f() {
int foo = 0;
int bar = 0;
foo++;
bar++;
// many more repeated operations in actual code
foo++;
bar++;
return foo+bar;
}
Абстрагируя повторяющийся код в отдельные функции, получаем
static void change_locals(int *foo_p, int *bar_p) {
*foo_p++;
*bar_p++;
}
int f() {
int foo = 0;
int bar = 0;
change_locals(&foo, &bar);
change_locals(&foo, &bar);
return foo+bar;
}
Я ожидаю, что компилятор встроит change_locals
функционировать и оптимизировать такие вещи, как *(&foo)++
в результирующем коде foo++
,
Если я правильно помню, получение адреса локальной переменной обычно предотвращает некоторые оптимизации (например, она не может быть сохранена в регистрах), но применимо ли это, когда по адресу не выполняется арифметика с указателями и она не выходит из функции? С большим change_locals
, будет ли это иметь значение, если он был объявлен inline
(__inline
в MSVC)?
Меня особенно интересует поведение компиляторов GCC и MSVC.
3 ответа
inline
(и все его кузены _inline
, __inline
...) игнорируются gcc. Он может включить все, что решит, является преимуществом, за исключением более низких уровней оптимизации.
Процедура кода gcc -O3 для x86:
.text
.p2align 4,,15
.globl f
.type f, @function
f:
pushl %ebp
xorl %eax, %eax
movl %esp, %ebp
popl %ebp
ret
.ident "GCC: (GNU) 4.4.4 20100630 (Red Hat 4.4.4-10)"
Возвращает ноль, потому что *ptr++ не делает то, что вы думаете. Исправление приращений до:
(*foo_p)++;
(*bar_p)++;
результаты в
.text
.p2align 4,,15
.globl f
.type f, @function
f:
pushl %ebp
movl $4, %eax
movl %esp, %ebp
popl %ebp
ret
Таким образом, он напрямую возвращает 4. Он не только встроил их, но и оптимизировал расчеты.
VC++ по сравнению с 2005 предоставляет аналогичный код, но он также создал недоступный код для change_locals()
, Я использовал командную строку
/O2 /FD /EHsc /MD /FA /c /TP
Если я правильно помню, получение адреса локальной переменной обычно предотвращает некоторые оптимизации (например, она не может быть сохранена в регистрах), но применимо ли это, когда по адресу не выполняется арифметика с указателями и она не выходит из функции?
Общий ответ таков: если компилятор может гарантировать, что никто не изменит значение за его спиной, его можно безопасно поместить в регистр.
Думайте об этом, как будто компилятор сначала выполняет встраивание, а затем преобразует все *&foo
(который является результатом встраивания) просто foo
прежде чем решить, должны ли они быть помещены в регистры в памяти в стеке.
С большим значением change_locals, будет ли это иметь значение, если он будет объявлен встроенным (__inline в MSVC)?
Опять же, вообще говоря, решает ли компилятор что-то встроить, делается с помощью эвристики. Если вы явно укажете, что хотите, чтобы что-то было встроенным, компилятор, вероятно, включит это в процесс принятия решения.
Я протестировал gcc 4.5, MSC и IntelC, используя это:
#include <stdio.h>
void change_locals(int *foo_p, int *bar_p) {
(*foo_p)++;
(*bar_p)++;
}
int main() {
int foo = printf("");
int bar = printf("");
change_locals(&foo, &bar);
change_locals(&foo, &bar);
printf( "%i\n", foo+bar );
}
И все они встроили / оптимизировали значение foo+bar, но также сгенерировали код для change_locals() (но не использовали его).
К сожалению, до сих пор нет гарантии, что они сделают то же самое для любой такой "локальной функции".
НКА:
__Z13change_localsPiS_:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %edx
movl 12(%ebp), %eax
incl (%edx)
incl (%eax)
leave
ret
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
pushl %ebx
subl $28, %esp
call ___main
movl $LC0, (%esp)
call _printf
movl %eax, %ebx
movl $LC0, (%esp)
call _printf
leal 4(%ebx,%eax), %eax
movl %eax, 4(%esp)
movl $LC1, (%esp)
call _printf
xorl %eax, %eax
addl $28, %esp
popl %ebx
leave
ret