当我有一个循环时:
for (int i = 0; i < SlowVariable; i++)
{
//
}
我知道在VB6中,每次循环迭代都会访问SlowVariable
,从而提高效率:
int cnt = SlowVariable;
for (int i = 0; i < cnt; i++)
{
//
}
我是否需要在GCC中进行相同的优化?或者只评估SlowVariable
一次?
答案 0 :(得分:5)
这被称为&#34;吊装&#34; <{1}}离开了。
编译器只有在每次都能证明SlowVariabele
的值相同且评估SlowVariabele
没有副作用时才能这样做。
因此,例如,考虑以下代码(我假设为了示例,通过指针访问是&#34;慢&#34;由于某种原因):
SlowVariabele
编译器不能(通常)提升,因为它知道所有人都会使用void foo1(int *SlowVariabele, int *result) {
for (int i = 0; i < *SlowVariabele; ++i) {
--*result;
}
}
调用它,因此result == SlowVariabele
的值在循环期间会发生变化。
另一方面:
*SlowVariabele
现在至少在原则上,编译器可以知道void foo2(int *result) {
int val = 12;
int *SlowVariabele = &val;
for (int i = 0; i < *SlowVariabele; ++i) {
--*result;
}
}
永远不会在循环中发生变化,因此它可以提升。它是否真的这样做是因为优化器的积极程度以及它对函数的分析有多好,但是我希望任何认真的编译器能够做到这一点。
类似地,如果使用指针调用val
,编译器可以确定(在调用站点处)是不相等的,并且如果调用内联,则编译器可以提升。这是foo1
的用途:
restrict
void foo3(int *restrict SlowVariabele, int *restrict result) {
for (int i = 0; i < *SlowVariabele; ++i) {
--*result;
}
}
(在C99中引入)表示&#34;您不能使用restrict
&#34;来调用此函数,并允许编译器提升。
类似地:
result == SlowVariabele
严格的别名规则意味着void foo4(int *SlowVariabele, float *result) {
for (int i = 0; i < *SlowVariabele; ++i) {
--*result;
}
}
和SlowVariable
不能引用相同的位置(或者程序还有未定义的行为),因此编译器也可以提升。
答案 1 :(得分:2)
通常,变量不能慢(或快),除非它们被映射到某种奇怪的内存(在这种情况下你通常想要声明它们volatile
)。 / p>
但实际上,使用局部变量可以为优化创造更多机会,并且效果可能非常明显。只有当它能够证明在循环中调用的函数不能读取或写该全局变量时,编译器才能“缓存”全局变量。当你在循环中调用外部函数时,编译器可能无法证明这一点。
答案 2 :(得分:1)
这取决于编译器如何优化,例如:
#include <stdio.h>
int main(int argc, char **argv)
{
unsigned int i;
unsigned int z = 10;
for( i = 0 ; i < z ; i++ )
printf("%d\n", i);
return 0;
}
如果使用gcc example.c -o example
编译它,结果代码将为:
0x0040138c <+0>: push ebp
0x0040138d <+1>: mov ebp,esp
0x0040138f <+3>: and esp,0xfffffff0
0x00401392 <+6>: sub esp,0x20
0x00401395 <+9>: call 0x4018f4 <__main>
0x0040139a <+14>: mov DWORD PTR [esp+0x18],0xa
0x004013a2 <+22>: mov DWORD PTR [esp+0x1c],0x0
0x004013aa <+30>: jmp 0x4013c4 <main+56>
0x004013ac <+32>: mov eax,DWORD PTR [esp+0x1c]
0x004013b0 <+36>: mov DWORD PTR [esp+0x4],eax
0x004013b4 <+40>: mov DWORD PTR [esp],0x403064
0x004013bb <+47>: call 0x401b2c <printf>
0x004013c0 <+52>: inc DWORD PTR [esp+0x1c]
0x004013c4 <+56>: mov eax,DWORD PTR [esp+0x1c] ; (1)
0x004013c8 <+60>: cmp eax,DWORD PTR [esp+0x18] ; (2)
0x004013cc <+64>: jb 0x4013ac <main+32>
0x004013ce <+66>: mov eax,0x0
0x004013d3 <+71>: leave
0x004013d4 <+72>: ret
0x004013d5 <+73>: nop
0x004013d6 <+74>: nop
0x004013d7 <+75>: nop
i
的值将从堆栈移动到eax
。eax
或i
与堆栈中z
的值进行比较。所有这一切都发生在每一轮。
如果使用gcc -O2 example.c -o example
优化代码,结果将为:
0x00401b70 <+0>: push ebp
0x00401b71 <+1>: mov ebp,esp
0x00401b73 <+3>: push ebx
0x00401b74 <+4>: and esp,0xfffffff0
0x00401b77 <+7>: sub esp,0x10
0x00401b7a <+10>: call 0x4018a8 <__main>
0x00401b7f <+15>: xor ebx,ebx
0x00401b81 <+17>: lea esi,[esi+0x0]
0x00401b84 <+20>: mov DWORD PTR [esp+0x4],ebx
0x00401b88 <+24>: mov DWORD PTR [esp],0x403064
0x00401b8f <+31>: call 0x401ae0 <printf>
0x00401b94 <+36>: inc ebx
0x00401b95 <+37>: cmp ebx,0xa ; (1)
0x00401b98 <+40>: jne 0x401b84 <main+20>
0x00401b9a <+42>: xor eax,eax
0x00401b9c <+44>: mov ebx,DWORD PTR [ebp-0x4]
0x00401b9f <+47>: leave
0x00401ba0 <+48>: ret
0x00401ba1 <+49>: nop
0x00401ba2 <+50>: nop
0x00401ba3 <+51>: nop
z
的值,因此它会将代码修改为for( i = 0 ; i < 10 ; i++ )
。如果编译器没有像这段代码那样知道z
的值:
#include <stdio.h>
void loop(unsigned int z) {
unsigned int i;
for( i = 0 ; i < z ; i++ )
printf("%d\n", i);
}
int main(int argc, char **argv)
{
unsigned int z = 10;
loop(z);
return 0;
}
结果将是:
0x0040138c <+0>: push esi
0x0040138d <+1>: push ebx
0x0040138e <+2>: sub esp,0x14
0x00401391 <+5>: mov esi,DWORD PTR [esp+0x20] ; (1)
0x00401395 <+9>: test esi,esi
0x00401397 <+11>: je 0x4013b1 <loop+37>
0x00401399 <+13>: xor ebx,ebx ; (2)
0x0040139b <+15>: nop
0x0040139c <+16>: mov DWORD PTR [esp+0x4],ebx
0x004013a0 <+20>: mov DWORD PTR [esp],0x403064
0x004013a7 <+27>: call 0x401b0c <printf>
0x004013ac <+32>: inc ebx
0x004013ad <+33>: cmp ebx,esi
0x004013af <+35>: jne 0x40139c <loop+16>
0x004013b1 <+37>: add esp,0x14
0x004013b4 <+40>: pop ebx
0x004013b5 <+41>: pop esi
0x004013b6 <+42>: ret
0x004013b7 <+43>: nop
z
将在一些未使用的寄存器esi
中结束,寄存器是最快的存储类别。i
,编译器使用ebx
来存储i
的值,也注册。毕竟,它取决于您使用的编译器和优化选项,但在所有情况下,C仍然比VB更快,更快。
答案 3 :(得分:0)
这取决于你的编译器,但我相信如果SlowVariable的值是常数,大多数当代编译器都会为你优化。
答案 4 :(得分:0)
“它”(语言)不说。它必须表现,就像每次评估变量一样。
优化编译器可以做很多聪明的事情,所以最好将这些微优化留给编译器。
如果您要通过手动路线进行优化,请务必 profile (= measure)并阅读生成的代码。
答案 5 :(得分:0)
实际上它依赖于“SlowVariable”和编译器的行为。如果您的慢变量是例如volatile,编译器不会做任何缓存它的工作,因为关键字volatile不允许它。如果它不是“易失性”,编译器很可能通过将其加载到寄存器中来优化对该变量的连续访问。