c# - 在 C# 中,x+=y 和 x=x+y(x 和 y 都是简单类型)之间是否存在任何性能差异?

标签 c# operator-keyword

<分区>

在 C/C++ 中,

The compound-assignment operators combine the simple-assignment operator with another binary operator. Compound-assignment operators perform the operation specified by the additional operator, then assign the result to the left operand. For example, a compound-assignment expression such as

expression1 += expression2

can be understood as

expression1 = expression1 + expression2

However, the compound-assignment expression is not equivalent to the expanded version because the compound-assignment expression evaluates expression1 only once, while the expanded version evaluates expression1 twice: in the addition operation and in the assignment operation.

(引自 Microsoft Docs)


例如:

  1. 对于i+=2;i会被直接修改而不创建任何新对象。
  2. 对于i=i+2;,首先会创建i的副本。复制的一个将被修改,然后被分配回i
        i_copied = i;
        i_copied += 2;
        i = i_copied;

如果没有编译器的任何优化,第二种方法将构造一个无用的实例,这会降低性能。


在 C# 中,不允许重载像 += 这样的运算符。和所有 simple types intdouble 被声明为 readonly struct(这是否意味着 C# 中的所有结构实际上都是不可变的?)。

我想知道在 C# 中,是否有某种表达式强制直接修改对象(至少对于简单类型),而不创建任何无用的实例。

此外,如果构造函数没有副作用,C# 编译器是否可以按预期将表达式 x=x+y 优化为 x+=y和解构器。

最佳答案


C#

当您将 C# 编译成 .NET 程序集时,代码使用 MSIL(Microsoft 中间语言)。这允许代码是可移植的。 .NET 运行时将对其进行 JIT 编译以供执行。

MSIL 是一种堆栈语言。它不知道目标硬件的详细信息(例如 CPU 有多少个寄存器)。只有一种写法:

    ldloc.0
    ldloc.1
    add
    stloc.0

在堆栈中加载第一个局部变量,加载第二个局部变量,添加*它们,从堆栈中设置第一个局部变量。

※: add 从堆栈中弹出两个元素,将它们相加,并将结果推回堆栈。

因此,x=x+yx+=y 将产生相同的代码。


当然,之后还有一些优化。 JIT 编译器会将其转换为实际的机器代码。

这是我用 SharpLab 看到的:

mov ecx, [ebp-4]
add ecx, [ebp-8]
mov [ebp-4], ecx

所以,我们将[ebp-4]复制到ecx中,添加[ebp-8],然后复制ecx 返回到 [ebp-4]

所以...寄存器 ecx 是一个无用的实例吗?


嗯,那就是 SharpLab,那就是 JIT。理论上,不同的编译器可以将代码转换为不同平台上的不同内容。

您可以将 .NET 代码 AOT 编译为 native 镜像,这将更加积极地进行优化。虽然,我看不到您将如何改进简单的添加。 哦,我知道,它可能会看到您没有使用此值并将其删除,或者可能会看到您总是添加相同的值并将其替换为常量。

可能值得注意的是,现代 .NET JIT 能够在执行期间继续优化代码(它会很快生成优化不佳的 native 代码版本,稍后 - 一旦准备就绪 - 将其替换为更好的代码版本)。这个决定来自这样一个事实,即在 JIT 运行时,性能取决于创建 native 代码所花费的时间和 native 代码运行所花费的时间。


C++

让我们看看 C++ 做了什么。这是我使用 godbolt 看到的 x = x + yx += y (默认设置※):

    mov     eax, DWORD PTR [rbp-8]
    add     DWORD PTR [rbp-4], eax
    mov     eax, DWORD PTR [rbp-4]

movaddmov 指令与我们从 SharpLab 获得的指令相匹配,但寄存器选择不同。

※: x86-64 gcc 9.3 with -g -o/tmp/compiler-explorer-compiler2020424-22672-17cap6k.bjoj/output.s -masm=intel -S -fdiagnostics-color=always/tmp/compiler-explorer-compiler2020424-22672-17cap6k.bjoj/example.cpp

添加编译器选项 -O 使代码消失。这是有道理的,因为我没有使用它。

关于c# - 在 C# 中,x+=y 和 x=x+y(x 和 y 都是简单类型)之间是否存在任何性能差异?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61984038/

相关文章:

c# - 基础平台游戏 Rectangle based collision problems

c# - 在 C# 中使用 XPath 选择空白节点

java - java "... "运算符做什么(在类构造函数中找到)

c++ - 为字符串类 C++ 重载 >> 运算符

C++编译器无法找到函数(与命名空间相关)

Java && 运算符

c# - 对是否使用 fixed with unsafe code 和 stackalloc 的困惑

c# - 获取具有动态文件夹名称的特殊文件夹

c# - ViewModel MVC 5 使用 2 个类创建, Controller 使用它

c++ - 运算符重载 "new"C++