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

时间:2015-06-24 12:49:42

标签: c# .net compiler-optimization dead-code

我在网络上经历了一些关于这个问题的问题,但我没有找到任何问题的答案,或者是for another language或者它没有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循环?

我使用

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

9 个答案:

答案 0 :(得分:27)

好吧,你的变量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");
}

由于循环有几个语句,比如比较和增量,它可以用于实现有点短延迟/等待时间。 (虽然不是这样做的好习惯)

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

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

}

代码中的循环不是死代码,就死代码而言,以下是死代码,并且将被优化掉。

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

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

答案 1 :(得分:15)

无法删除循环 ,代码 not dead ,例如:

  // 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的所有可能结果,这就是它保留循环的原因

答案 2 :(得分:6)

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

j++;

和比较

j<TestRunTime.Length;

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

在其他循环中也会发生这种情况。

答案 3 :(得分:4)

它不会被忽视。但是,当你到达IL时会有一个跳转语句,因此for将像if语句一样运行。正如@Fleve所提到的,它还将运行++和长度的代码。它只是额外的代码。为了便于阅读,以及遵守代码标准,如果您不使用代码,我会删除代码。

答案 4 :(得分:4)

  

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

您无法轻易确定它无用,因此编译器也无法做到。例如,TestRunTime.Length的吸气剂会产生副作用。

  

背景是我保留了很多代码(我没有写过),我想知道无用的代码是否应该成为目标

在重构一段代码之前,你必须验证它的作用才能更改它,然后说它仍然具有相同的结果。单元测试是一种很好的方法。

答案 5 :(得分:4)

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

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

如果您想确定删除的内容以及生成的x86代码的内容。 C#编译器很少进行优化。 JIT做了一些。

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

答案 6 :(得分:4)

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

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

如果TestRunTime.Length没有副作用且未使用j,则删除循环。

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

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

接下来,编译器可以确定是否需要赋值TestRunTime.Length。它可以替换为仅确定TestRunTime.Length的代码。

当然,你的编译器可能不会尝试任何花哨的优化,或者语言规则可能会使它无法确定这些事情,而你却陷入困境。

答案 7 :(得分:3)

在大多数情况下,您不必担心主动删除无用的代码。如果遇到性能问题,并且你的探查器说一些无用的代码正在吃掉你的时钟周期,那就去核吧。但是,如果代码真的没有做任何副作用,那么它对运行时间的影响可能很小。

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

即使在JIT环境中,像我描述的那样的循环几乎肯定会被窥视优化到mov eax 1000000。它可能会进一步优化,但没有其他背景,优化是合理的,不会产生任何不良影响。

tl; dr:如果您的探查器说死/无用代码使用了大量的运行时资源,请将其删除。尽管如此,不要继续追捕死码!把它留给重写/大规模重构。

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

答案 8 :(得分:1)

根据一些回答关于使用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    |
---------------------------------------------

因此,即使使用数组,它也不会得到优化。