我在网络上经历了一些关于这个问题的问题,但我没有找到任何问题的答案,或者是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循环?
背景是我维护了很多代码(我没写)我想知道无用的代码是否应该是目标,或者我是否可以让编译器处理
答案 0 :(得分:27)
好吧,你的变量i
和prevSpec_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++)
{
}
...
通常,编译器只是无法预测1}} 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 |
---------------------------------------------
因此,即使使用数组,它也不会得到优化。