我写了一些代码来测试 try-catch 的影响,但看到了一些令人惊讶的结果。
static void Main(string[] args) { Thread.CurrentThread.Priority = ThreadPriority.Highest; Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime; long start = 0, stop = 0, elapsed = 0; double avg = 0.0; long temp = Fibo(1); for (int i = 1; i < 100000000; i++) { start = Stopwatch.GetTimestamp(); temp = Fibo(100); stop = Stopwatch.GetTimestamp(); elapsed = stop - start; avg = avg + ((double)elapsed - avg) / i; } Console.WriteLine("Elapsed: " + avg); Console.ReadKey(); } static long Fibo(int n) { long n1 = 0, n2 = 1, fibo = 0; n++; for (int i = 1; i < n; i++) { n1 = n2; n2 = fibo; fibo = n1 + n2; } return fibo; }
在我的电脑上,这始终打印出一个大约 0.96 的值。
当我用这样的 try-catch 块将 for 循环包装在 Fibo() 中时:
static long Fibo(int n) { long n1 = 0, n2 = 1, fibo = 0; n++; try { for (int i = 1; i < n; i++) { n1 = n2; n2 = fibo; fibo = n1 + n2; } } catch {} return fibo; }
现在它始终打印出 0.69… - 它实际上运行得更快!但为什么?
注意:我使用 Release 配置编译它并直接运行 EXE 文件(在 Visual Studio 之外)。
编辑:Jon Skeet 的出色分析表明 try-catch 以某种方式导致 x86 CLR 在这种特定情况下以更有利的方式使用 CPU 寄存器(我认为我们还没有理解为什么)。我证实了 Jon 的发现,即 x64 CLR 没有这种差异,而且它比 x86 CLR 更快。我还在intFibo 方法中使用类型而不是long类型进行了测试,然后 x86 CLR 与 x64 CLR 一样快。
int
long
更新:看起来这个问题已经被 Roslyn 修复了。相同的机器,相同的 CLR 版本——使用 VS 2013 编译时问题仍然存在,但使用 VS 2015 编译时问题消失了。
一位专门了解堆栈使用优化的Roslyn工程师查看了这个并向我报告,C# 编译器生成局部变量存储的方式与JIT编译器确实注册的方式之间的交互似乎存在问题在相应的 x86 代码中进行调度。结果是在本地人的加载和存储上生成次优代码。
由于某些我们所有人都不清楚的原因,当 JITter 知道该块位于受尝试保护的区域中时,会避免有问题的代码生成路径。
这很奇怪。我们将跟进 JITter 团队,看看我们是否可以输入错误,以便他们修复这个问题。
此外,我们正在努力改进 Roslyn 对 C# 和 VB 编译器的算法,以确定何时可以将本地变量设置为“临时”——也就是说,只需在堆栈上推送和弹出,而不是在堆栈上分配特定位置激活的持续时间。我们相信 JITter 将能够更好地完成寄存器分配等工作,如果我们能更好地提示本地人何时可以更早地“死亡”。
感谢您引起我们的注意,并为奇怪的行为道歉。