zjy*_*qs 2 c# operator-keyword
在 C/C++ 中,
复合赋值运算符将简单赋值运算符与另一个二元运算符组合在一起。复合赋值运算符执行附加运算符指定的操作,然后将结果分配给左操作数。例如,复合赋值表达式,如
expression1 += expression2可以理解为
expression1 = expression1 + expression2然而,复合赋值表达式并不等同于扩展版本,因为复合赋值表达式只计算 expression1 一次,而扩展版本计算 expression1 两次:在加法运算和赋值运算中。
(引自Microsoft Docs)
例如:
i+=2;,i将直接修改而不创建任何新对象。i=i+2;,i首先会创建一个副本。复制的一个将被修改,然后被分配回i. i_copied = i;
i_copied += 2;
i = i_copied;
Run Code Online (Sandbox Code Playgroud)
如果没有编译器的任何优化,第二种方法将构造一个无用的实例,从而降低性能。
在 C# 中,+=不允许重载类似运算符。并且所有像or一样的简单类型都声明为(这是否意味着 C# 中的所有结构实际上都是不可变的?)。intdoublereadonly struct
我不知道在C#,是有一定的表达力对象被修改(至少对于简单类型)直接,而不被创建的任何无用实例。
而且,是否有可能在C#-compiler优化表达x=x+y到x+=y不如预期,如果有一个从构造函数和deconstructors无副作用。
将 C# 编译为 .NET 程序集时,代码采用 MSIL(Microsoft 中间语言)。这允许代码是可移植的。.NET 运行时将编译它 JIT 以执行。
MSIL 是一种堆栈语言。它不知道目标硬件的详细信息(例如 CPU 有多少个寄存器)。只有一种方法可以编写该加法:
ldloc.0
ldloc.1
add
stloc.0
Run Code Online (Sandbox Code Playgroud)
加载堆栈中的第一个本地,加载第二个,添加?他们,从堆栈中设置第一个本地。
?: add从堆栈中弹出两个元素,将它们相加,并将结果压回到堆栈中。
因此,两者x=x+y和x+=y将产生相同的代码。
当然,之后会发生一些优化。JIT 编译器会将其转换为实际的机器代码。
这是我在SharpLab 中看到的:
mov ecx, [ebp-4]
add ecx, [ebp-8]
mov [ebp-4], ecx
Run Code Online (Sandbox Code Playgroud)
所以,我们复制[ebp-4]到ecx,添加[ebp-8]到它,然后复制ecx回[ebp-4]。
那么......寄存器ecx是一个无用的实例吗?
嗯,那是 SharpLab,那是 JIT。理论上,不同的编译器可以将代码转换为不同平台上的不同内容。
您可以将 .NET 代码 AOT 编译为本机映像,这将更积极地进行优化。虽然,我不知道你将如何改进一个简单的添加。哦,我知道,它可能会看到您没有使用此值并将其删除,或者可能会看到您总是添加相同的值并用常量替换它。
可能值得注意的是,现代 .NET JIT 能够在执行期间继续优化代码(它会迅速生成代码的优化不佳的本机版本,稍后 - 一旦准备就绪 - 将其替换为更好的版本)。这个决定来自这样一个事实,即在 JIT 运行时上,性能取决于创建本机代码所需的时间和运行本机代码所需的时间。
让我们看看 C++ 做了什么。这就是我看到的x = x + y和x += y使用Godbolt(默认设置?)的情况:
mov eax, DWORD PTR [rbp-8]
add DWORD PTR [rbp-4], eax
mov eax, DWORD PTR [rbp-4]
Run Code Online (Sandbox Code Playgroud)
说明mov, add,mov符合我们从SharpLab得到的,具有不同的选择寄存器。
?: x86-64 gcc 9.3 -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使代码消失。这是有道理的,因为我没有使用它。
| 归档时间: |
|
| 查看次数: |
209 次 |
| 最近记录: |