Delphi 优化:常量循环
Delphi optimisation : constant loop
我刚刚注意到我正在编写的程序中有一些非常有趣的东西。我有一个简单的过程,它用 x.
类型的对象填充 TStringlist
我在跟踪问题时添加了一个断点,偶然发现了这个问题,希望有人能解释为什么会这样,或者 link 找到相关文档,因为我找不到任何东西.
我的循环从 0 到 11。我正在使用的指针在循环中由 for nPtr := 0 初始化,但是当程序是 运行 时,nPtr var 从 12 下降到1. 然后我在循环外初始化了 var,如代码片段所示,但同样的事情发生了。该变量在本单元的其他任何地方都没有使用。
我问过一个与我共事的人,他说这是由于 Delphi 优化造成的,但我想知道为什么以及它如何决定应该影响哪个循环。
感谢您的帮助。
代码:
procedure TUnit.ProcedureName;
var
nPtr : Integer;
obj : TObject;
begin
nPtr:=0;//added later
for nPtr := 0 to 11 do
begin
obj := TObject.Create(Self);
slPeriodList.AddObject('X', obj);
end;
end;
只有在循环体不引用循环变量的情况下才能进行优化。在那种情况下,如果循环的下界为零,则编译器将反转循环。
如果循环变量从未被循环体引用,那么编译器可以随心所欲地实现循环。它所需要做的就是执行循环体,次数与循环边界规定的一样多。实际上,编译器完全有理由优化掉循环变量。
考虑这个程序:
{$APPTYPE CONSOLE}
procedure Test1;
var
i: Integer;
begin
for i := 0 to 11 do
Writeln(0);
end;
procedure Test2;
var
i: Integer;
begin
for i := 0 to 11 do
Writeln(i);
end;
begin
Test1;
Test2;
end.
Test1
的主体由 XE7、32 位 Windows 编译器编译成此代码,发布选项:
Project1.dpr.9: for i := 0 to 11 do
00405249 BB0C000000 mov ebx,[=25=]00000c
Project1.dpr.10: Writeln(0);
0040524E A114784000 mov eax,[[=25=]407814]
00405253 33D2 xor edx,edx
00405255 E8FAE4FFFF call @Write0Long
0040525A E8D5E7FFFF call @WriteLn
0040525F E800DBFFFF call @_IOTest
Project1.dpr.9: for i := 0 to 11 do
00405264 4B dec ebx
00405265 75E7 jnz [=25=]40524e
编译器是运行向下循环,使用dec
可以看出。请注意,循环终止测试是使用 jnz
执行的,不需要 cmp
。这是因为 dec
对零执行隐式比较。
dec
的文档说明如下:
Flags Affected
The CF flag is not affected. The OF, SF, ZF, AF, and PF flags are set
according to the result.
当且仅当 dec
指令的结果为零时,才会设置 ZF
标志。而 ZF
决定了 jnz
是否分支。
为 Test2
发出的代码是:
Project1.dpr.17: for i := 0 to 11 do
0040526D 33DB xor ebx,ebx
Project1.dpr.18: Writeln(i);
0040526F A114784000 mov eax,[[=26=]407814]
00405274 8BD3 mov edx,ebx
00405276 E8D9E4FFFF call @Write0Long
0040527B E8B4E7FFFF call @WriteLn
00405280 E8DFDAFFFF call @_IOTest
00405285 43 inc ebx
Project1.dpr.17: for i := 0 to 11 do
00405286 83FB0C cmp ebx,[=26=]c
00405289 75E4 jnz [=26=]40526f
请注意,循环变量在增加,我们现在有一个额外的 cmp
指令,在每次循环迭代时执行。
有趣的是,64 位 Windows 编译器不包含此优化。对于 Test1
它产生这个:
Project1.dpr.9: for i := 0 to 11 do
00000000004083A5 4833DB xor rbx,rbx
Project1.dpr.10: Writeln(0);
00000000004083A8 488B0D01220000 mov rcx,[rel [=27=]002201]
00000000004083AF 4833D2 xor rdx,rdx
00000000004083B2 E839C3FFFF call @Write0Long
00000000004083B7 4889C1 mov rcx,rax
00000000004083BA E851C7FFFF call @WriteLn
00000000004083BF E86CB4FFFF call @_IOTest
00000000004083C4 83C301 add ebx,
Project1.dpr.9: for i := 0 to 11 do
00000000004083C7 83FB0C cmp ebx,[=27=]c
00000000004083CA 75DC jnz Test1 +
我不确定为什么这个优化没有在 64 位编译器中实现。我的猜测是优化在现实世界中的影响可以忽略不计并且设计者选择不花费精力为 64 位编译器实现它。
我刚刚注意到我正在编写的程序中有一些非常有趣的东西。我有一个简单的过程,它用 x.
类型的对象填充 TStringlist我在跟踪问题时添加了一个断点,偶然发现了这个问题,希望有人能解释为什么会这样,或者 link 找到相关文档,因为我找不到任何东西.
我的循环从 0 到 11。我正在使用的指针在循环中由 for nPtr := 0 初始化,但是当程序是 运行 时,nPtr var 从 12 下降到1. 然后我在循环外初始化了 var,如代码片段所示,但同样的事情发生了。该变量在本单元的其他任何地方都没有使用。
我问过一个与我共事的人,他说这是由于 Delphi 优化造成的,但我想知道为什么以及它如何决定应该影响哪个循环。
感谢您的帮助。
代码:
procedure TUnit.ProcedureName;
var
nPtr : Integer;
obj : TObject;
begin
nPtr:=0;//added later
for nPtr := 0 to 11 do
begin
obj := TObject.Create(Self);
slPeriodList.AddObject('X', obj);
end;
end;
只有在循环体不引用循环变量的情况下才能进行优化。在那种情况下,如果循环的下界为零,则编译器将反转循环。
如果循环变量从未被循环体引用,那么编译器可以随心所欲地实现循环。它所需要做的就是执行循环体,次数与循环边界规定的一样多。实际上,编译器完全有理由优化掉循环变量。
考虑这个程序:
{$APPTYPE CONSOLE}
procedure Test1;
var
i: Integer;
begin
for i := 0 to 11 do
Writeln(0);
end;
procedure Test2;
var
i: Integer;
begin
for i := 0 to 11 do
Writeln(i);
end;
begin
Test1;
Test2;
end.
Test1
的主体由 XE7、32 位 Windows 编译器编译成此代码,发布选项:
Project1.dpr.9: for i := 0 to 11 do 00405249 BB0C000000 mov ebx,[=25=]00000c Project1.dpr.10: Writeln(0); 0040524E A114784000 mov eax,[[=25=]407814] 00405253 33D2 xor edx,edx 00405255 E8FAE4FFFF call @Write0Long 0040525A E8D5E7FFFF call @WriteLn 0040525F E800DBFFFF call @_IOTest Project1.dpr.9: for i := 0 to 11 do 00405264 4B dec ebx 00405265 75E7 jnz [=25=]40524e
编译器是运行向下循环,使用dec
可以看出。请注意,循环终止测试是使用 jnz
执行的,不需要 cmp
。这是因为 dec
对零执行隐式比较。
dec
的文档说明如下:
Flags Affected
The CF flag is not affected. The OF, SF, ZF, AF, and PF flags are set according to the result.
当且仅当 dec
指令的结果为零时,才会设置 ZF
标志。而 ZF
决定了 jnz
是否分支。
为 Test2
发出的代码是:
Project1.dpr.17: for i := 0 to 11 do 0040526D 33DB xor ebx,ebx Project1.dpr.18: Writeln(i); 0040526F A114784000 mov eax,[[=26=]407814] 00405274 8BD3 mov edx,ebx 00405276 E8D9E4FFFF call @Write0Long 0040527B E8B4E7FFFF call @WriteLn 00405280 E8DFDAFFFF call @_IOTest 00405285 43 inc ebx Project1.dpr.17: for i := 0 to 11 do 00405286 83FB0C cmp ebx,[=26=]c 00405289 75E4 jnz [=26=]40526f
请注意,循环变量在增加,我们现在有一个额外的 cmp
指令,在每次循环迭代时执行。
有趣的是,64 位 Windows 编译器不包含此优化。对于 Test1
它产生这个:
Project1.dpr.9: for i := 0 to 11 do 00000000004083A5 4833DB xor rbx,rbx Project1.dpr.10: Writeln(0); 00000000004083A8 488B0D01220000 mov rcx,[rel [=27=]002201] 00000000004083AF 4833D2 xor rdx,rdx 00000000004083B2 E839C3FFFF call @Write0Long 00000000004083B7 4889C1 mov rcx,rax 00000000004083BA E851C7FFFF call @WriteLn 00000000004083BF E86CB4FFFF call @_IOTest 00000000004083C4 83C301 add ebx, Project1.dpr.9: for i := 0 to 11 do 00000000004083C7 83FB0C cmp ebx,[=27=]c 00000000004083CA 75DC jnz Test1 +
我不确定为什么这个优化没有在 64 位编译器中实现。我的猜测是优化在现实世界中的影响可以忽略不计并且设计者选择不花费精力为 64 位编译器实现它。