【问题标题】:Why does .NET Native compile loop in reverse order?为什么 .NET Native 以相反的顺序编译循环?
【发布时间】:2016-07-25 15:01:12
【问题描述】:

我正在研究 .NET Native 编译器执行的优化技术。 我创建了一个示例循环:

        for (int i = 0; i < 100; i++)
        {
            Function();
        }

我已经用 Native 编译了它。然后我在IDA中用机器码反汇编了结果.dll文件。结果,我有:

(我已经删除了一些不必要的行,所以不用担心地址行不一致)

我知道add esi, 0FFFFFFFFh 的真正含义是subtract one from esi and alter Zero Flag if needed,所以如果还没有达到零,我们可以跳到开头。

我不明白的是为什么编译器要反转循环?

我的结论是

LOOP:
add esi, 0FFFFFFFFh
jnz LOOP

只是比例如更快

LOOP:
inc esi
cmp esi, 064h
jl LOOP

但真的是因为这个,速度差异真的很大吗?

【问题讨论】:

  • 带有立即值的 ADD 比 INC 更快,并且您还可以跳过 CMP ......所有这些都在 3 行代码中。那么是的,差异非常显着(无论是在大小上还是在速度上)。想象一下,在一个真实世界的程序中,在大约 30000 个地方执行此操作......
  • 是的,它更快,而且通常优化器会应用任何可以使您的代码更快而不改变程序语义的优化。
  • 是的,因为您甚至不需要比较。你可以(不)看到:)
  • 您已经以两种方式编写了代码。如果您想知道一种方式是否比另一种更快,请运行它们
  • @EricLippert 我并不懒惰,我很愿意,但我现在在我的工作电脑上,我没有任何工具可以运行或对汇编代码进行基准测试 :( 我也没有'没有安装任何东西的管理员权限。

标签: c# assembly x86 micro-optimization .net-native


【解决方案1】:

inc might be slower than add because of the partial flag update。此外add 会影响零标志,因此您不需要使用另一个cmp 指令。直接跳就好了。

这是loop optimization的一种著名类型

反转:循环反转反转将值分配给索引变量的顺序。这是一个微妙的优化,可以帮助消除依赖关系,从而实现其他优化。此外,某些架构在汇编语言级别使用循环结构,这些结构仅在一个方向上计数(例如,如果非零递减跳转 (DJNZ))。

你可以看到其他编译器的结果here

【讨论】:

  • incadd 慢一个时钟周期。在Intel® 64 and IA-32 Architectures Optimization Reference Manual 中比较它们。向下滚动到附录 C,您可以看到每条 x86/x64 指令的延迟和吞吐量时间。 1 个时钟周期可能看起来并不重要,但如果你有数百或数千个循环,它会很快加起来。
  • @Icemanind 这些数字并不能反映他们描述的微架构的实际情况(IvyBridge 到 Skylake;参见该附录前面的表格)。 dec/jnz 循环每个周期可以运行 1 次迭代,而 inc/dec 作为其他 dep 链的一部分的整数寄存器只有 1 个周期延迟。可能英特尔通过 Broadwell(但不是 Skylake)在 IvyBridge 上通过查看延迟来读取 EFLAGS 获得了 2 个周期的延迟,可能包括需要标志合并的 CF。但这对于 dec / jnz 来说不是问题,即使没有融合或dec / setz。我只有一个 Skylake,所以我无法测试:/
  • @Icemanind:还要注意那些是 latency 数字;您引用的表格仍然列出了0.25 个周期的 inc/dec 吞吐量,即每时钟 4 个。无论如何,Agner Fog 的指令表基于实验测试,列出了 1c 延迟/0.25c 吞吐量的 inc/dec。 uops.info/table.html 也是如此。 uops.info 甚至测量了从输入到整数输出和标记输出的延迟,并在两种情况下都发现了 1 个周期:uops.info/html-instr/INC_R32.html。 (不包括CF非输出)
【解决方案2】:

你的结论是正确的:倒置循环将针对0(当寄存器值达到0时循环将结束),因此Add将在条件分支中设置零标志。

这样您就不需要专用的Cmp,这会导致:1) 大小优化 2) 它也更快(来自编译器程序员的决定和另一个answer 的结论)。

这是编写针对0 的循环的非常常见的汇编程序技巧。我很惊讶您了解汇编程序,但不知道(询问)它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-19
    • 2019-03-13
    • 1970-01-01
    • 2015-05-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多