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?

c#编译器优化

如果不假设变量x没有与您正在运行的方法同时访问,则编译器无法执行此优化。否则,它可能会以一种可检测的方式改变方法的行为。

考虑这样一种情况:this引用的对象是从两个线程并发访问的。Tread A重复设置x为零;线程B重复调用x1()

如果编译器将x1优化为相当于x2,那么实验后x的两个可观察状态将是02:

  • 如果AB之前完成,则得到2
  • 如果BA之前完成,则得到0

如果A在中间抢占了B,你仍然会得到一个2

然而,x1的原始版本允许三种结果:x可以最终为0, 12

  • 如果AB之前完成,则得到2
  • 如果BA之前完成,则得到0
  • 如果B在第一次增量后被抢占,然后A完成,然后B运行完成,你得到1

x1x2不一样:

如果x是一个公共字段,并且是在多线程环境中访问的,那么第二个线程完全有可能在两个调用之间改变x,这在x2中的代码中是不可能的。

对于y2,如果+和/或*x类型重载,则x*x + x*x 可能与2*x*x不同

编译器将优化如下内容(无论如何不是一个详尽的列表):

  • 删除不使用的局部变量(释放寄存器)
  • 删除不影响逻辑流或输出的代码。
  • 内联调用简单方法

编译器优化不应该改变程序的行为(尽管它确实会发生)。因此,重新排序/组合数学运算超出了优化的范围。

把它写成可读的,编译器会做剩下的工作。

嗯,编译器可能会对进行一些优化,但是在设计时仍然可以做很多事情来提高性能。是的,可读的代码肯定是有价值的,但是编译器的工作是生成与你的源代码相对应的工作 IL,而不是为了更快而修改你的源代码。