【问题标题】:Delphi optimisation : constant loopDelphi优化:恒定循环
【发布时间】:2015-04-01 13:05:20
【问题描述】:

我刚刚注意到我正在编写的程序中有一些非常有趣的东西。我有一个简单的过程,用 x 类型的对象填充 TStringlist。

我在跟踪问题时添加了一个断点,偶然发现了这个问题,希望有人能够解释为什么会发生这种情况,或者链接到相关文档,因为我找不到任何东西。

我的循环从 0 到 11。我正在使用的指针在循环中由 for nPtr := 0 初始化,但是当程序运行时,nPtr var 从 12 下降到 1。然后我初始化了var 在循环之外,如代码 sn-p 所示,但同样的事情发生了。该变量在单元中的其他任何地方都没有使用。

我问过一位与我一起工作的人,他说这是由于 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;

【问题讨论】:

  • 这是调试器中的一个限制(或错误,取决于您的苛刻程度)。大卫在他的回答中解释了编译器在做什么,这很好,但恕我直言,调试器应该真的知道这个转换已经执行并反转(消除)观察变量的可观察效果,​​等等。唉,它没有。
  • ISTM 对调试器的要求太多了。它要么显示“由于优化而无法访问 nPtr”的不祥消息,要么显示真实值,向您展示幕后发生的事情。我更喜欢后者(这就是它现在所做的)。让它看起来好像循环索引没有上升,这不是调试器的任务。

标签: performance delphi for-loop optimization compiler-optimization


【解决方案1】:

只有在循环体不引用循环变量时才可能进行优化。在这种情况下,如果循环的下限为零,则编译器将反转循环。

如果循环体从不引用循环变量,那么编译器可以根据需要执行循环。所需要做的就是按照循环边界的要求多次执行循环体。事实上,编译器完全有理由优化循环变量。

考虑这个程序:

{$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,$0000000c Project1.dpr.10:Writeln(0); 0040524E A114784000 移动 eax,[$00407814] 00405253 33D2 xor edx,edx 00405255 E8FAE4FFFF 调用@Write0Long 0040525A E8D5E7FFFF 调用@WriteLn 0040525F E800DBFFFF 调用@_IOTest Project1.dpr.9: for i := 0 to 11 do 00405264 4B 十二月 ebx 00405265 75E7 jnz $0040524e

编译器正在向下运行循环,这可以通过使用dec 看出。请注意,循环终止测试是使用jnz 执行的,不需要cmp。那是因为dec 执行与零的隐式比较。

dec 的文档说明如下:

受影响的标志

CF 标志不受影响。 OF、SF、ZF、AF 和 PF 标志已设置 根据结果​​。

当且仅当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 移动 eax,[$00407814] 00405274 8BD3 移动 edx,ebx 00405276 E8D9E4FFFF 调用@Write0Long 0040527B E8B4E7FFFF 调用@WriteLn 00405280 E8DFDAFFFF 调用@_IOTest 00405285 43 公司 ebx Project1.dpr.17: for i := 0 to 11 do 00405286 83FB0C cmp ebx,$0c 00405289 75E4 jnz $0040526f

请注意,循环变量正在增加,我们现在有一个额外的 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 $00002201] 00000000004083AF 4833D2 xor rdx,rdx 00000000004083B2 E839C3FFFF 调用@Write0Long 00000000004083B7 4889C1 mov rcx,rax 00000000004083BA E851C7FFFF 调用@WriteLn 00000000004083BF E86CB4FFFF 调用@_IOTest 00000000004083C4 83C301 添加 ebx,$01 Project1.dpr.9: for i := 0 to 11 do 00000000004083C7 83FB0C cmp ebx,$0c 00000000004083CA 75DC jnz 测试1 + 8 美元

我不确定为什么在 64 位编译器中没有实现这种优化。我的猜测是优化在现实世界中的影响可以忽略不计,设计人员选择不花精力为 64 位编译器实现它。

【讨论】:

  • The optimization is only possible if the loop body does not refer to the loop variable。这并不完全正确。仅当访问循环变量的顺序对结果不重要时才有可能,即它没有可见的副作用并且最终结果相同。所以for I := 0 to 9 do A[I] := 0;确实引用了循环中的循环变量,但是它没有可见的副作用,如果向后运行也不会改变结果。
  • @RudyVelthuis 这也需要A 是一个尚未被捕获的局部变量。你看到的是公平的。我认为编译器实际上不会分析类似的东西。而且我们实际上没有像 C/C++“as-if”规则这样的正式规则。
  • 但我同意,在现实世界中,它的影响可能可以忽略不计。
  • @DavidHeffernan: A 可以是任何数组或指针。无论循环向下还是向上,最终效果都是一样的。
  • @Rudy 和 David,FWIW,在对数组元素的如此简单引用中,编译器预先计算第一个受影响元素的地址,然后根据 add(或 downto 循环中的 sub)到元素大小。 IOW,它甚至不使用循环控制变量来索引数组。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-20
  • 1970-01-01
  • 2021-01-21
相关资源
最近更新 更多