哪个更快? ++、+= 还是 x + 1?

Posted

技术标签:

【中文标题】哪个更快? ++、+= 还是 x + 1?【英文标题】:Which is faster? ++, += or x + 1? 【发布时间】:2011-09-22 14:32:02 【问题描述】:

我正在使用 C#(这个问题也适用于 C++ 等类似语言),我正在尝试找出最快、最有效的增量方式。这不仅仅是一两个增量,在我的游戏中,它就像每秒 300 个增量。就像屏幕上每个精灵的帧都在增加一样,我的 rpg 角色的速度和位置,相机的偏移量等。所以我在想,最有效的方法是什么?例如,在我能做的每一个动作上增加 5 y_pos

1.

Player.YPos += 5;

2.

Player.YPos = Player.YPos + 5;

3.

for (int i = 0; i < 5; i++)

    Player.YPos++;

哪个效率最高(最快)?

【问题讨论】:

这是你代码的瓶颈吗? “过早的优化是万恶之源”。 它只是简单的伪代码作为示例来说明我的观点 @burning:为了表明你的观点,profile 我很好奇在什么时候有人认为,“嗯,(1) 看起来有点不确定,我最好将其实现为 (3)。”... @Black Bear 我花了一个多星期的时间用 C++ 制作了一个简单的磁贴管理器,我在一天内用 C# 重新制作了它(如果算上调试,则需要 2 天)。所以我不会回去了!您知道,开发时间也很重要。 【参考方案1】:

它们是一样的:

static void Main(string[] args)

    int a = 0;
    a++;
    a +=1;
    a = a+1;

上面ILSpy中的代码是:

private static void Main(string[] args)

    int a = 0;
    a++;
    a++;
    a++;

所有这些的 IL 也相同(在发布模式下):

.method private hidebysig static void  Main(string[] args) cil managed

    .entrypoint
    // Code size       15 (0xf)
    .maxstack  2
    .locals init ([0] int32 a)
    IL_0000:  ldc.i4.0
    IL_0001:  stloc.0
    IL_0002:  ldloc.0
    IL_0003:  ldc.i4.1
    IL_0004:  add
    IL_0005:  stloc.0
    IL_0006:  ldloc.0
    IL_0007:  ldc.i4.1
    IL_0008:  add
    IL_0009:  stloc.0
    IL_000a:  ldloc.0
    IL_000b:  ldc.i4.1
    IL_000c:  add
    IL_000d:  stloc.0
    IL_000e:  ret
 // end of method Program::Main

【讨论】:

【参考方案2】:

编译器应该为 1 和 2 生成相同的程序集,并且它可能在选项 3 中展开循环。面对这样的问题时,您可以使用一个有用的工具用于凭经验测试发生了什么是查看编译器生成的程序集。在 g++ 中,这可以使用 -S 开关来实现。

例如,当使用命令g++ -S inc.cpp(使用 g++ 4.5.2)编译时,选项 1 和 2 都会生成此汇编程序


main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    addl    $5, -4(%rbp)
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

g++ 为选项 3 生成效率显着降低的汇编程序:


main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    movl    $0, -8(%rbp)
    jmp .L2
.L3:
    addl    $1, -4(%rbp)
    addl    $1, -8(%rbp)
.L2:
    cmpl    $4, -8(%rbp)
    setle   %al
    testb   %al, %al
    jne .L3
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

但是通过优化(甚至 -O1)g++ 会为所有 3 个选项产生这个:


main:
.LFB0:
    .cfi_startproc
    leal    5(%rdi), %eax
    ret
    .cfi_endproc

g++ 不仅在选项 3 中展开循环,而且还使用 lea instruction 在单个指令中进行加法运算,而不是与 mov 胡闹。

因此,g++ 将始终为选项 1 和 2 生成相同的程序集。只有当您明确打开优化(这是您可能期望的行为)时,g++ 才会为所有 3 个选项生成相同的程序集。

(看起来你也应该可以inspect the assembly produced by C#,虽然我从未尝试过)

【讨论】:

C# 编译器不生成汇编器 @大卫:是的。抱歉,我应该更具体一些 - 我的帖子底部的链接提供了工具的详细信息,如果我理解正确,可以反汇编 C# 生成的 MSIL;实际上允许您查看从 C# 代码生成的程序集,即使 C# 编译器不会自行输出程序集(至少,这是我的理解 - 我不使用 C#,所以我无法对此进行测试)。 @David 也许不是,但无论他使用哪种语言,这都是 OP 应该采取的确切方法。 老兄这是 C# 问题而不是 cpp @NitinJS OP 确实声明“这个问题也适用于 C++ 等类似语言”,并且标记为 C++ 和 C#。【参考方案3】:

(针对 C# 的答案,因为 C++ 可能会有很大差异。)

1 和 2 是等价的。

3 肯定会更慢。

话虽如此,每秒仅执行 300 次,您不会注意到任何差异。您是否知道计算机在一秒钟内可以处理多少原始 CPU + 内存?一般来说,您应该为清晰编写代码作为最重要的事情。无论如何都要担心性能 - 但只有当您有办法衡量它时,才能 a) 判断您是否需要担心,以及 b) 是否有任何更改实际上会提高性能。 p>

在这种情况下,我会说选项 1 是最清楚的,所以这就是我要使用的。

【讨论】:

但是智能编译器可以检测到副作用的缺乏,展开循环然后将五个增量操作优化为一个添加,对吗? :) @burningprodigy:我希望“加 5”可能需要与“增加 1”一样少的周期。但如有疑问,请测试 @Frédéric 一个聪明的编译器可能确实意识到了这一点,但我希望编写我的编译器的人能够将他们的精力放在有用的优化上。 @Jon, @David,值得我用 g++ 4.4.5 尝试循环:从-O2 开始,优化器发出一条addl $5, %eax 指令。我想优化是免费的,更重要的是:) @Frédéric 好吧,你知道什么。这些天来,您似乎必须竭尽全力编写运行缓慢的代码!【参考方案4】:

选项 1 和 2 将在编译后产生相同的代码。选项 3 会慢得多,因为它会为所涉及的 for 循环产生更多代码。

【讨论】:

@NitinJS - 是的,生成的机器代码将包含更多代码行。【参考方案5】:

选项 1 和 2 将导致编译器生成相同的代码。选项 3 会慢很多。

i++i += 1 甚至i = i + 1 更快是一个谬论。所有体面的编译器都会将这三个指令转换为相同的代码。

对于加法这种微不足道的操作,写最清晰的代码,让编译器操心让它变快。

【讨论】:

选项 2 不会比选项 1 慢吗?? @Beriel 为什么会这样?编译器将为 1 和 2 发出相同的代码。 实际上,++ii++ 快,但前提是 i 是一个对象,甚至在某些情况下也是如此。 @vsz 为了我自己的理智,我不使用前置和后置增量运算符作为右值,所以我并不真正关心这些事情。更重要的是这个问题是关于 int 的。

以上是关于哪个更快? ++、+= 还是 x + 1?的主要内容,如果未能解决你的问题,请参考以下文章

哪个更快?组合查询还是多个查询?

MySQL:在 1:N 关系中哪个更快?加入还是两个查询? [复制]

charAt() 还是子字符串?哪个更快?

get() 还是 merge() 哪个更快?

哪个更快,水平计数还是垂直计数?

ArrayList 还是 LinkedList 哪个运行得更快? [复制]