Gcc автоматически "разворачивает" операторы if?
Скажем, у меня есть цикл, который выглядит следующим образом:
for(int i = 0; i < 10000; i++) {
/* Do something computationally expensive */
if (i < 200 && !(i%20)) {
/* Do something else */
}
}
где какая-то тривиальная задача застревает за оператором if, который выполняется всего несколько раз. Я всегда слышал, что "операторы if в циклах медленные!" Итак, в надежде на (незначительное) повышение производительности я разделил циклы на:
for(int i = 0; i < 200; i++) {
/* Do something computationally expensive */
if (!(i%20)) {
/* Do something else */
}
}
for(int i = 200; i < 10000; i++) {
/* Do something computationally expensive */
}
Будет ли gcc (с соответствующими флагами, такими как -O3) автоматически разбивать один цикл на два, или он будет развернут только для уменьшения количества итераций?
1 ответ
Почему бы просто не разобрать программу и убедиться в этом? Но здесь мы идем. Это тестовая программа:
int main() {
int sum = 0;
int i;
for(i = 0; i < 10000; i++) {
if (i < 200 && !(i%20)) {
sum += 0xC0DE;
}
sum += 0xCAFE;
}
printf("%d\n", sum);
return 0;
}
и это интересная часть дизассемблированного кода, скомпилированного с gcc 4.3.3 и -o3:
0x08048404 <main+20>: xor ebx,ebx
0x08048406 <main+22>: push ecx
0x08048407 <main+23>: xor ecx,ecx
0x08048409 <main+25>: sub esp,0xc
0x0804840c <main+28>: lea esi,[esi+eiz*1+0x0]
0x08048410 <main+32>: cmp ecx,0xc7
0x08048416 <main+38>: jg 0x8048436 <main+70>
0x08048418 <main+40>: mov eax,ecx
0x0804841a <main+42>: imul esi
0x0804841c <main+44>: mov eax,ecx
0x0804841e <main+46>: sar eax,0x1f
0x08048421 <main+49>: sar edx,0x3
0x08048424 <main+52>: sub edx,eax
0x08048426 <main+54>: lea edx,[edx+edx*4]
0x08048429 <main+57>: shl edx,0x2
0x0804842c <main+60>: cmp ecx,edx
0x0804842e <main+62>: jne 0x8048436 <main+70>
0x08048430 <main+64>: add ebx,0xc0de
0x08048436 <main+70>: add ecx,0x1
0x08048439 <main+73>: add ebx,0xcafe
0x0804843f <main+79>: cmp ecx,0x2710
0x08048445 <main+85>: jne 0x8048410 <main+32>
0x08048447 <main+87>: mov DWORD PTR [esp+0x8],ebx
0x0804844b <main+91>: mov DWORD PTR [esp+0x4],0x8048530
0x08048453 <main+99>: mov DWORD PTR [esp],0x1
0x0804845a <main+106>: call 0x8048308 <__printf_chk@plt>
Итак, как мы видим, для этого конкретного примера, нет, это не так. У нас есть только один цикл, начинающийся с main+32 и заканчивающийся на main+85. Если у вас проблемы с чтением ассемблерного кода ecx = i; ebx = сумма
Но, тем не менее, ваш пробег может варьироваться - кто знает, какие эвристики используются для этого конкретного случая, поэтому вам придется скомпилировать код, который вы имели в виду, и посмотреть, как более длинные / более сложные вычисления влияют на оптимизатор.
Несмотря на то, что на любом современном процессоре предсказатель ветвления будет очень хорош в таком простом коде, вы не увидите значительных потерь производительности в любом случае. Какая потеря производительности, возможно, из-за нескольких неправильных прогнозов, если ваш интенсивный вычислительный код требует миллиардов циклов?