ADD 1是否比INC更快?x86

19

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


10
因为这取决于微架构和上下文,所以他需要在许多不同的CPU上进行复杂的测试。如果可以直接询问,为什么要那么麻烦呢? - harold
1
@哈罗德:公平地说,每个人都可以自己测试。唯一需要的材料是一台x86机器、一个汇编器和一个秒表。制作一个指令流以展示差异需要一点创造力,但这并不是火箭科学(说到这一点,“火箭科学”也不是火箭科学)。 - Stephen Canon
5
真的,这个有点难。如果只是“加上 vs 和”,或者类似这样的东西,那么没问题,任何人都能想出来。但这完全不同。大多数人只会在循环中添加“inc”和“add”,然后得出结论它们没有区别。而且没有迹象表明答案是不准确的。 - harold
1
@哈罗德:毫无疑问;当我第一次遇到这个障碍(编写大数加法例程)时,我花了好3或4个小时才弄清楚发生了什么。 - Stephen Canon
关闭这个问题是相当荒谬的,因为有人在4年后发布了一个类似的问题。我的问题是关于这个主题的第一个,并且清楚地阐述了问题。对我的问题的回答或多或少是确定的。如果有什么问题应该被关闭,那就是另一个问题,而不是我的问题。 - Tyler Durden
显示剩余7条评论
2个回答

34
在某些微架构和某些指令流中,INC将会发生“部分标志更新延迟”(因为它会更新一些标志而保留其他标志)。ADD设置所有标志的值,因此不会冒这种风险。ADD并不总是比INC更快,但几乎总是至少与INC一样快(在某些较旧的微架构上有一些特殊情况,但它们非常罕见),有时甚至更快。有关详细信息,请参阅英特尔优化参考手册Agner Fog的微架构笔记

1
今天无论如何,当我开始编程时情况完全不同。那时候INC更快。 :-) - Brian Knoblauch
3
当 P4 处于流行时期时,人们更偏向于使用 add。现在 P4 已经几乎被淘汰,大多数情况下人们更喜欢使用 inc,因为它更短,并且和 add 一样快。如果你想避免修改进位标志,则可以使用 lea reg, [reg+1],这样就不会修改 任何 标志,避免了可怕的部分标志延迟。或者,如果可能的话,尽量避免在标志生成器和标志消费器之间进行递增。 AMD K8 到 Steamroller,以及 Intel P6 / Sandybridge 家族都会单独跟踪不同标志位的标志依赖性。例如 CF 由自己跟踪,以避免像 inc 这样的错误依赖。 - Peter Cordes
1
更新:自Skylake(也许还包括Broadwell)以来,英特尔从未合并FLAGS,CF和其他标志(SPAZO)仅被指令如cmovbe作为2个单独的输入读取。大多数cmov指令都是1个uop,但那些需要EFLAGS的两个部分的仍然是现代英特尔上的2个uops。(请参见@BeeOnRope在什么是Partial Flag Stall?上的回答)。但这意味着inc / dec即使在ADC循环中也是完全有效的;没有标志合并,因此lea reg,[reg + 1]没有优势。 - Peter Cordes

6
虽然这并不是一个确定的答案,但请写下这个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的作者都认为INCADD reg, 1更好。那么对于您的问题,我会相信他们的判断,认为INCADD一样快,并且由于较短的寄存器编码而节省了一个字节,使其更可取。编译器作者只是人,所以他们可能会错,但这不太可能。 :)
进一步的实验表明,如果您不使用-march = native选项,则gcc将使用add ebx,1,而Clang则始终喜欢inc最好。我的结论是,当您在2012年提出问题时,ADD有时更可取,但是现在在2016年,您应该始终选择INC

1
是的,查看编译器选择的内容通常是一个好策略。(即使在2012年,“inc”仍然完全可以,尽管那时P4已经不相关了。)我注意到gcc的指令成本估计似乎更注重延迟而不是吞吐量。也许这是一般的好策略。例如,它将使用两个“lea”指令来替换对常数的乘法,即使针对Haswell进行调整。clang通过使用“imul r32,r32,imm”来乘以小常数,除非它可以使用单个LEA(如“lea eax,[rcx + rcx * 4]”)来优先考虑代码大小/指令计数/吞吐量。 - Peter Cordes
这实际上已经不再是真的了:https://godbolt.org/z/Nup-2I。GCC在`-O0`到`-O3`中使用`add ebx,1,在-Os中使用inc ebx`。 - r00ster
3
要让gcc使用inc而不是add,您需要添加-march=native - Björn Lindqvist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接