【问题标题】:Relative performance of x86 inc vs. add instructionx86 inc 与 add 指令的相对性能
【发布时间】:2011-08-24 23:59:30
【问题描述】:

快速提问,事先假设

mov eax, 0

哪个更高效?

inc eax
inc eax

add eax, 2

另外,如果两个incs 更快,编译器(例如,GCC)是否通常(即没有积极的优化标志)优化var += 2

PS:不要费心用“不要过早优化”的变体来回答,这只是学术兴趣。

【问题讨论】:

  • 答案可能是特定于处理器的,在大多数情况下很可能没有可测量的差异。如果您真的有兴趣了解特定 CPU 的答案,请对其进行基准测试。

标签: performance assembly optimization x86 increment


【解决方案1】:

出于所有目的,这可能无关紧要。但请考虑到 inc 使用较少的字节。

考虑以下代码:

int x = 0;
x += 2;

在不使用任何优化标志的情况下,GCC 将此代码编译为:

80483ed:       c7 44 24 1c 00 00 00    movl   $0x0,0x1c(%esp)
80483f4:       00 
80483f5:       83 44 24 1c 02          addl   $0x2,0x1c(%esp)

使用-O1-O2,就变成了:

c7 44 24 08 02 00 00    movl   $0x2,0x8(%esp)

很有趣,不是吗?

【讨论】:

    【解决方案2】:

    如果您想了解 x86 指令的原始性能统计信息,请参阅Dr Agner Fogs listings(确切地说是第 4 卷)。至于编译器的部分,那是依赖于编译器的代码生成器,而不是你应该过度依赖的东西。

    附带说明:我觉得有趣/讽刺的是,在关于性能的问题中,您使用 MOV EAX,0 将寄存器归零而不是 XOR EAX,EAX :P(如果事先完成了 MOV EAX,0,最快的变体将删除公司和添加的,只是MOV EAX,2)。

    【讨论】:

      【解决方案3】:

      同一寄存器上的两条inc 指令(或更一般地说,两条读取-修改-写入指令)总是具有至少两个周期的依赖链。这是假设 inc 有一个时钟延迟,自 486 以来就是这种情况。这意味着如果周围的指令不能与两条 inc 指令交错以隐藏这些延迟,代码将执行得更慢。

      但是无论如何,没有编译器会发出您建议的指令序列(mov eax,0 将被xor eax,eax 替换,请参阅What is the purpose of XORing a register with itself?

      mov eax,0
      inc eax
      inc eax
      

      会优化到

      mov eax,2
      

      【讨论】:

      • 请注意,xor eax, eax; inc eaxmov eax, 1 更受大多数编译器的青睐。可能是因为它是 3 个字节而不是 5 个。
      • @LưuVĩnhPhúc mov eax, 1 是 5 个字节:b8 01 00 00 00。由于 8 字节文字和 QWORD 前缀:48 b8 01 00 00 00 00 00 00 00,因此 64 位为 10 字节。相比之下,xor rax, rax; inc eax 只有 5 个字节:48 31 c0 ff c0
      • @Polynomial:所有现代主流编译器都将使用mov eax,1,除非您明确告诉他们优化大小而不是速度(godbolt.org/z/Kn7jE5 - clang 或 ICC -Os -m32 或 MSVC -O1 将在 32 位模式下使用 xor/incgcc -Os -m32 仍然使用 mov)。在优化速度时,节省 2 个字节的代码大小不值得为后端增加一个 uop(或为前端解码的额外指令)。 clang -Oz 优化大小而不关心速度将在 64 位模式下使用 push 1 / pop rax。所有这些编译器都使用 mov 和普通选项。
      • @phuclv:你可能应该清理你之前的 cmets;早期的几个错误(但也在多项式的第二条评论中,64位的字节数膨胀)。选项是mov eax, 1(5 个字节)通过隐式零扩展设置 RAX=1,或push 1/pop rax(3 个字节)或xor eax,eax/inc eax(4 个字节)。但是编译器只使用mov,除非优化大小而不是速度。 Tips for golfing in x86/x64 machine code 指出来自另一个已知值寄存器的 3 字节 lea eax, [rdx+1] 可能很有用
      【解决方案4】:

      从您可以找到here 的英特尔手册中,看起来 ADD/SUB 指令在一个特定架构上要便宜半个周期。但请记住,英特尔对其(最近的)处理器使用了乱序执行模型。这主要意味着,性能瓶颈出现在处理器必须等待数据进入的任何地方(例如,它在 L1/L2/L3/RAM 数据获取期间无事可做)。因此,如果您的分析器告诉您 INC 可能是问题所在;从数据吞吐量的角度来看待它,而不是查看原始循环计数。

      Instruction              Latency1           Throughput         Execution Unit 
                                                                  2 
      CPUID                    0F_3H    0F_2H      0F_3H    0F_2H    0F_2H 
      
      ADD/SUB                  1        0.5        0.5      0.5      ALU 
      [...]
      DEC/INC                  1        1          0.5      0.5      ALU 
      

      【讨论】:

      • IIRC 0f_2h 是 P4 Prescott,愿他安息。这些半时钟延迟是由内部双时钟流水线造成的。事实证明,这对英特尔来说是一个非常糟糕的主意。
      猜你喜欢
      • 2016-07-30
      • 2012-11-03
      • 2012-05-06
      • 2016-06-17
      • 2016-08-17
      • 2015-06-25
      • 2015-01-05
      相关资源
      最近更新 更多