c#编译器优化
本文关键字:优化 编译器 | 更新日期: 2023-09-27 18:05:48
为什么编译器做优化我的代码?
我有两个功能:
public void x1() {
x++;
x++;
}
public void x2() {
x += 2;
}
public void x3() {
x = x + 2;
}
public void y3() {
x = x * x + x * x;
}
这就是我在发布模式下编译后看到的ILSpy:
// test1.Something
public void x1()
{
this.x++;
this.x++;
}
// test1.Something
public void x2()
{
this.x += 2;
}
// test1.Something
public void x3()
{
this.x += 2;
}
// test1.Something
public void y3()
{
this.x = this.x * this.x + this.x * this.x;
}
x2和x3也可以。但是为什么x1没有优化到相同的结果呢?没有理由保持它的2步增量?为什么y3不是x=2*(x*x)
?这不应该比x*x+x*x
快吗?
这就引出了这个问题?如果不是这样简单的事情,c#编译器会做什么样的优化呢?
当我读到你经常听到的关于编写代码的文章时,把它写得可读,编译器会做剩下的工作。但是在这种情况下,编译器几乎什么也不做。
再添加一个示例:
public void x1() {
int a = 1;
int b = 1;
int c = 1;
x = a + b + c;
}
和使用ILSpy:
// test1.Something
public void x1()
{
int a = 1;
int b = 1;
int c = 1;
this.x = a + b + c;
}
为什么不是这个?X = 3?
如果不假设变量x
没有与您正在运行的方法同时访问,则编译器无法执行此优化。否则,它可能会以一种可检测的方式改变方法的行为。
考虑这样一种情况:this
引用的对象是从两个线程并发访问的。Tread A
重复设置x
为零;线程B
重复调用x1()
。
如果编译器将x1
优化为相当于x2
,那么实验后x
的两个可观察状态将是0
和2
:
- 如果
A
在B
之前完成,则得到2
- 如果
B
在A
之前完成,则得到0
如果A
在中间抢占了B
,你仍然会得到一个2
。
然而,x1
的原始版本允许三种结果:x
可以最终为0
, 1
或2
。
- 如果
A
在B
之前完成,则得到2
- 如果
B
在A
之前完成,则得到0
- 如果
B
在第一次增量后被抢占,然后A
完成,然后B
运行完成,你得到1
。
x1
和x2
不一样:
如果x
是一个公共字段,并且是在多线程环境中访问的,那么第二个线程完全有可能在两个调用之间改变x
,这在x2
中的代码中是不可能的。
对于y2
,如果+
和/或*
对x
类型重载,则x*x + x*x
可能与2*x*x
不同。
编译器将优化如下内容(无论如何不是一个详尽的列表):
- 删除不使用的局部变量(释放寄存器)
- 删除不影响逻辑流或输出的代码。
- 内联调用简单方法
编译器优化不应该改变程序的行为(尽管它确实会发生)。因此,重新排序/组合数学运算超出了优化的范围。
嗯,编译器可能会对进行一些优化,但是在设计时仍然可以做很多事情来提高性能。是的,可读的代码肯定是有价值的,但是编译器的工作是生成与你的源代码相对应的工作 IL,而不是为了更快而修改你的源代码。把它写成可读的,编译器会做剩下的工作。