【问题标题】:Is ADD 1 really faster than INC ? x86 [duplicate]ADD 1 真的比 INC 快吗? x86 [重复]
【发布时间】:2012-11-03 05:16:24
【问题描述】:

我阅读了各种优化指南,声称 ADD 1 比在 x86 中使用 INC 更快。这是真的吗?

【问题讨论】:

  • @A.Webb 因为它取决于微架构和上下文。他必须对许多不同的 cpu 进行复杂的测试。如果你可以问,为什么要这样做?
  • @harold:如果是在他测试它和我们测试它以在 Stack Overflow 上为他写答案之间,我选择他来做。
  • @harold:公平地说,每个人都可以自己测试。唯一需要的材料是 x86 机器、组装机和秒表。制作一个指令流来展示差异需要一点创造力,但这不是火箭科学(就此而言,火箭科学不是火箭科学)。
  • 真的,伙计们,这很难。如果它是“add vs and”或类似的东西,那么可以肯定,任何人都可以弄清楚。但这完全不同。大多数人只会在循环中抛出incadd,他们会得出结论认为没有区别。而且没有迹象表明答案不准确。
  • @harold:毫无疑问;当我第一次遇到这个摊位时,我花了 3 到 4 个小时才弄清楚发生了什么(编写一个 bignum 加法例程)。

标签: performance optimization assembly x86


【解决方案1】:

在一些微架构上,带有一些指令流,INC 将导致“部分标志更新停顿”(因为它更新了一些标志,同时保留了其他标志)。 ADD 设置所有标志的值,因此不会冒这样的停滞。

ADD 并不总是比INC 快,但它几乎总是至少一样快(在某些较旧的微架构上存在一些极端情况,但它们非常罕见),有时甚至更快。

更多详情,请咨询Intel's Optimization Reference ManualAgner Fog's micro-architecture notes

【讨论】:

  • 无论如何,今天和我开始编程时完全不同。那时INC更快。 :-)
  • 当 P4 是最新的时,add 是首选。现在 P4 或多或少已经死了和埋没了,inc 在大多数情况下是首选,因为它更短,并且运行速度与add 相同。如果您想避免修改进位标志,请使用 lea reg, [reg+1] 不修改 any 标志,避免可怕的部分标志停顿。或者如果可能,避免在标志生产者和标志消费者之间进行增量。 AMD K8 通过 Steamroller 和 Intel P6 / Sandybridge 系列都针对不同的标志位分别跟踪标志依赖性。例如CF 被自己跟踪,以避免像inc 那样的错误deps
  • 更新:英特尔因为 Skylake(可能还有 Broadwell)从不合并 FLAGS、CF 和其他标志 (SPAZO),只需通过 cmovbe 等需要两者的指令读取为 2 个单独的输入。大多数 cmov 指令是 1 uop,但是在现代 Intel 上需要 EFLAGS 的两个部分的指令仍然是 2 uop。 (请参阅@BeeOnRope 在What is a Partial Flag Stall? 上的回答)。但这意味着inc/dec 即使在 ADC 循环中也是完全高效的;没有标志合并,所以对lea reg, [reg+1]没有优势。
【解决方案2】:

虽然这不是一个明确的答案。编写这个 C 文件:

=== inc.c ===
#include <stdio.h>
int main(int argc, char *argv[])
{
    for (int n = 0; n < 1000; n++) {
        printf("%d\n", n);
    }
    return 0;
}

然后运行:

clang -march=native -masm=intel -O3 -S -o inc.clang.s inc.c
gcc -march=native -masm=intel -O3 -S -o inc.gcc.s inc.c

注意生成的汇编代码。相关的clang输出:

mov     esi, ebx
call    printf
inc     ebx
cmp     ebx, 1000
jne     .LBB0_1

相关 gcc 输出:

mov     edi, 1
inc     ebx
call    __printf_chk
cmp     ebx, 1000
jne     .L2

这证明了 clang 和 gcc 的作者都认为 INC 在现代架构上是比 ADD reg, 1 更好的选择。

这对您的问题意味着什么?好吧,我相信他们对您阅读的指南的判断并得出结论,INCADD 一样快,并且由于寄存器编码较短而节省的一个字节使其更可取。编译器作者只是人,所以他们可能会出错,但这不太可能。 :)

更多的实验告诉我,如果你不使用 -march=native 选项,那么 gcc 将使用 add ebx, 1 代替。 Clang otoh,总是最喜欢 inc。我的结论是,当您在 2012 年提出问题时,ADD 有时更可取,但现在在 2016 年,您应该始终选择INC

【讨论】:

  • 是的,看看编译器选择什么通常是一个好策略。 (即使在 2012 年,inc 也完全没问题。当时 P4 已经无关紧要了。)我注意到 gcc 的指令成本估算似乎更多地关注延迟而不是吞吐量。总的来说,这可能是一个很好的策略。例如它将使用两个lea 指令来替换乘以一个常数,即使在为 Haswell 进行调整时也是如此。 clang 确实更喜欢代码大小/insn 计数/吞吐量,通过使用 imul r32, r32, imm 乘以小常数,除非它可以使用单个 LEA(如 lea eax, [rcx+rcx*4])。
  • 这实际上不再正确:godbolt.org/z/Nup-2I。 GCC 将add ebx, 1 用于-O0-O3inc ebx 用于-Os
  • 您必须添加-march=native 才能让gcc 使用inc 而不是add
猜你喜欢
  • 2016-07-30
  • 2011-08-24
  • 1970-01-01
  • 2011-09-28
  • 2019-04-30
  • 1970-01-01
  • 2016-10-21
相关资源
最近更新 更多