我的编译器会忽略无用的代码吗?

Will my compiler ignore useless code?

我已经通过网络回答了几个关于这个主题的问题,但我没有找到我的问题的任何答案,或者它是for another language or it doesn't answer totally(死代码是不是 无用的代码)所以这是我的问题:

编译器是否忽略(显式或非显式)无用代码?

例如,在这段代码中:

double[] TestRunTime = SomeFunctionThatReturnDoubles;
// A bit of code skipped
int i = 0;
for (int j = 0; j < TestRunTime.Length; j++)
{

}
double prevSpec_OilCons = 0;

for 循环会被移除吗?

我用 and


背景是我维护了很多代码(我没有写),我想知道无用的代码是否应该成为目标,或者我是否可以让编译器会处理这个问题。

在您的循环中,每次迭代中都隐含了两个操作。增量:

j++;

与比较

j<TestRunTime.Length;

所以,循环不是空的,尽管它看起来是空的。最后有一些东西正在执行,这当然不会被编译器忽略。

这也发生在其他循环中。

它不会被忽略。然而,当你到达 IL 时会有一个跳转语句,所以 for 将 运行 就好像它是一个 if 语句一样。正如@Fleve 提到的,它还将 运行 ++ 和长度的代码。这只是额外的代码。为了可读性以及与代码标准保持一致,如果您不使用代码,我会删除它。

好吧, 你的变量 iprevSpec_OilCons,如果不在任何地方使用,都会被优化掉,但不会优化你的循环。

因此,如果您的代码如下所示:

static void Main(string[] args)
{
    int[] TestRunTime = { 1, 2, 3 };
    int i = 0;
    for (int j = 0; j < TestRunTime.Length; j++)
    {

    }
    double prevSpec_OilCons = 0;
    Console.WriteLine("Code end");
}

ILSpy下它将是:

private static void Main(string[] args)
{
    int[] TestRunTime = new int[]
    {
        1,
        2,
        3
    };
    for (int i = 0; i < TestRunTime.Length; i++)
    {
    }
    Console.WriteLine("Code end");
}

由于循环有几个语句,如比较和增量,它可以用于实现 有点 短 delay/wait 周期。 (虽然这样做不是一个好习惯)

考虑下面的循环,这是一个空循环,但是执行起来会花费很多时间。

for (long j = 0; j < long.MaxValue; j++)
{

}

你代码中的循环,不是死码,就死码而言,下面是死码,会被优化掉。

if (false)
{
    Console.Write("Shouldn't be here");
}

循环,甚至不会被 .NET 抖动消除。基于此answer

Is (explicit or not) useless code ignored by the compiler?

您不能轻易确定它没用,因此编译器也不能。例如,TestRunTime.Length 的 getter 可能会有副作用。

The background is that I maintain a lot of code (that I didn't write) and I was wondering if useless code should be a target

在你重构一段代码之前,你必须验证它做了什么才能改变它并在之后说它仍然有相同的结果。单元测试是执行此操作的好方法。

JIT 基本上可以去除死代码。它不是很彻底。死变量和表达式被可靠地杀死。这是 SSA 形式的简单优化。

不确定控制流。如果嵌套两个循环,我记得只有内部循环会被删除。

如果您想确定哪些已删除,哪些未删除,请查看生成的 x86 代码。 C# 编译器做的优化很少。 JIT 做了一些。

4.5 32 位和 64 位 JIT 是不同的代码库,具有不同的行为。一个新的 JIT (RyuJIT) 即将推出,在我的测试中通常表现更差,有时更好。

循环不能去掉,代码是没死,例如:

  // Just some function, right?
  private static Double[] SomeFunctionThatReturnDoubles() {
    return null;
  }

  ...
  double[] TestRunTime = SomeFunctionThatReturnDoubles();
  ...
  // You'll end up with exception since TestRunTime is null
  for (int j = 0; j < TestRunTime.Length; j++)
  {
  }
  ...

通常,编译器 无法预测 SomeFunctionThatReturnDoubles 所有可能的结果,这就是为什么它保留循环

显然你的编译器不会忽略无用的代码,但会仔细分析它然后尝试删除它,如果它执行优化。

在你的例子中,第一个有趣的事情是变量 j 是否在循环之后使用。另一个有趣的事情是TestRunTime.Length。编译器会查看它并检查它是否总是 return 相同的结果,如果是,它是否有任何副作用,如果是,调用它一次是否与重复调用它具有相同的副作用。

如果 TestRunTime.Length 没有副作用并且不使用 j 则循环被删除。

否则,如果重复调用TestRunTime.Length比调用一次有更多的副作用,或者如果重复调用return不同的值,则必须执行循环。

否则,j = max (0, TestRunTime.Length)。

接下来,编译器可以判断是否需要赋值TestRunTime.Length。它可能会被仅确定 TestRunTime.Length 是什么的代码所取代。

当然你的编译器可能不会尝试任何花哨的优化,或者语言规则可能无法确定这些东西,你就被卡住了。

在大多数情况下,您不必担心主动删除无用代码。如果你 运行 遇到性能问题,并且你的分析器说一些无用的代码正在占用你的时钟周期,那么就去解决它。但是,如果代码真的什么都不做也没有副作用,那么它可能对 运行宁时间影响不大。

也就是说,大多数编译器不需要执行任何优化,因此依赖编译器优化并不总是最明智的选择。但在许多情况下,即使是无用的自旋循环也可以执行得非常快。循环一百万次的基本自旋锁会编译成类似 mov eax, 0 \ inc eax \ cmp eax, 1000000 \ jnz -8 的东西。即使我们不考虑 on-CPU 优化,每个循环也只有 3 个周期(在最近的 RISC 样式芯片上),因为没有内存访问,所以不会有任何缓存失效。在 1GHz CPU 上,只有 3,000,000/1,000,000,000 秒,即 3 毫秒。如果您尝试 运行每秒 60 次,那将是一个非常重要的打击,但在许多情况下,它甚至可能不会引人注意。

即使在 JIT 环境中,像我描述的那样的循环几乎肯定会针对 mov eax 1000000 进行窥孔优化。它可能会进一步优化,但在没有其他上下文的情况下,优化是合理的并且不会造成不良影响。

tl;dr:如果您的探查器说 dead/useless 代码正在使用您的 运行 时间资源的明显数量,请将其删除。不过,不要继续 寻找死代码;将其留给 rewrite/massive 重构。

好处:如果代码生成器知道 eax 除了循环条件之外不会被读取,并且想要保留自旋锁,它可以生成 mov eax, 1000000 \ dec eax \ jnz -3 并减少一个循环的循环惩罚。不过,大多数编译器会完全删除它。

我根据一些回答者关于使用long.MaxValue的想法制作了一个小表格来测试它,这是我的参考代码:

public Form1()
{
    InitializeComponent();
    Stopwatch test = new Stopwatch();
    test.Start();
    myTextBox.Text = test.Elapsed.ToString();
}

这里是带有 有点 无用代码的代码:

public Form1()
{
    InitializeComponent();
    Stopwatch test = new Stopwatch();
    test.Start();
    for (int i = 0; i < int.MaxValue; i++)
    {
    }
    myTextBox.Text = test.Elapsed.ToString();
}

你会说我用 int.MaxValue 而不是 long.MaxValue,我不想把 的一天花在这一天上。

如您所见:

---------------------------------------------------------------------
|                   |   Debug               |   Release             |
---------------------------------------------------------------------
|Ref                |   00:00:00.0000019    |   00:00:00.0000019    |
|Useless code       |   00:00:05.3837568    |   00:00:05.2728447    |
---------------------------------------------------------------------

代码未优化。等一下,我会尝试用一些 int[] 来测试 int[].Lenght:

public Form1()
{
    InitializeComponent();
    int[] myTab = functionThatReturnInts(1);

    Stopwatch test = new Stopwatch();
    test.Start();
    for (int i = 0; i < myTab.Length; i++)
    {

    }
    myTextBox.Text = test.Elapsed.ToString();
}
public int[] functionThatReturnInts(int desiredSize)
{
    return Enumerable.Repeat(42, desiredSize).ToArray();
}

结果如下:

---------------------------------------------
|   Size            |   Release             |
---------------------------------------------
|             1     |   00:00:00.0000015    |
|           100     |   00:00:00            |
|        10 000     |   00:00:00.0000035    |
|     1 000 000     |   00:00:00.0003236    |
|   100 000 000     |   00:00:00.0312673    |
---------------------------------------------

所以即使使用数组,它也根本没有得到优化。