在一次微软员工的代码检查中,我们在一个try{}块中发现了一大块代码。她和一位IT代表表示,这可能会对代码的性能产生影响。事实上,他们建议大部分代码应该在try/catch块之外,并且只检查重要的部分。这位微软员工补充说,即将发布的白皮书对错误的尝试/捕获块提出了警告。
我环顾四周,发现它会影响优化,但它似乎只适用于在作用域之间共享变量时。
我不是在问代码的可维护性,甚至不是在问如何处理正确的异常(毫无疑问,有问题的代码需要重构)。我也不是指使用异常进行流控制,这在大多数情况下显然是错误的。这些都是重要的问题(有些更重要),但不是这里的重点。
当不抛出异常时,try/catch块如何影响性能?
是的,尝试/捕捉会“损害”性能(一切都是相对的)。就浪费的CPU周期而言,这并不多,但还有其他重要的方面需要考虑:
代码大小
方法内联
基准
首先,让我们使用一些复杂的工具(例如BenchmarkDotNet)检查速度。编译为Release (AnyCPU),在x64机器上运行。我想说没有区别,即使测试确实会告诉我们NoTryCatch()是一个很小很小的一点快:
| Method | N | Mean | Error | StdDev |
|------------------ |---- |---------:|----------:|----------:|
| NoTryCatch | 0.5 | 3.770 ns | 0.0492 ns | 0.0411 ns |
| WithTryCatch | 0.5 | 4.060 ns | 0.0410 ns | 0.0384 ns |
| WithTryCatchThrow | 0.5 | 3.924 ns | 0.0994 ns | 0.0881 ns |
分析
一些额外的注释。
| Method | Code size | Inlineable |
|------------------ |---------- |-----------:|
| NoTryCatch | 12 | yes |
| WithTryCatch | 18 | ? |
| WithTryCatchThrow | 18 | no |
代码大小NoTryCatch()在代码中产生12个字节,而try/catch则增加6个字节。此外,每当编写try/catch语句时,您很可能会有一个或多个throw new Exception(“Message”,ex)语句,进一步“膨胀”代码。
这里最重要的是代码内联。在. net中,仅仅throw关键字的存在就意味着该方法永远不会被编译器内联(意味着代码更慢,但占用空间更少)。我最近彻底测试了这个事实,所以它在。net Core中似乎仍然有效。不确定try/catch是否遵循相同的规则。待办事项:验证!
完整的测试代码
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
namespace TryCatchPerformance
{
public class TryCatch
{
[Params(0.5)]
public double N { get; set; }
[Benchmark]
public void NoTryCatch() => Math.Sin(N);
[Benchmark]
public void WithTryCatch()
{
try
{
Math.Sin(N);
}
catch
{
}
}
[Benchmark]
public void WithTryCatchThrow()
{
try
{
Math.Sin(N);
}
catch (Exception ex)
{
throw;
}
}
}
class Program
{
static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<TryCatch>();
}
}
}
检查它。
static public void Main(string[] args)
{
Stopwatch w = new Stopwatch();
double d = 0;
w.Start();
for (int i = 0; i < 10000000; i++)
{
try
{
d = Math.Sin(1);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
w.Stop();
Console.WriteLine(w.Elapsed);
w.Reset();
w.Start();
for (int i = 0; i < 10000000; i++)
{
d = Math.Sin(1);
}
w.Stop();
Console.WriteLine(w.Elapsed);
}
输出:
00:00:00.4269033 // with try/catch
00:00:00.4260383 // without.
以毫秒为单位:
449
416
新代码:
for (int j = 0; j < 10; j++)
{
Stopwatch w = new Stopwatch();
double d = 0;
w.Start();
for (int i = 0; i < 10000000; i++)
{
try
{
d = Math.Sin(d);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
d = Math.Sin(d);
}
}
w.Stop();
Console.Write(" try/catch/finally: ");
Console.WriteLine(w.ElapsedMilliseconds);
w.Reset();
d = 0;
w.Start();
for (int i = 0; i < 10000000; i++)
{
d = Math.Sin(d);
d = Math.Sin(d);
}
w.Stop();
Console.Write("No try/catch/finally: ");
Console.WriteLine(w.ElapsedMilliseconds);
Console.WriteLine();
}
新结果:
try/catch/finally: 382
No try/catch/finally: 332
try/catch/finally: 375
No try/catch/finally: 332
try/catch/finally: 376
No try/catch/finally: 333
try/catch/finally: 375
No try/catch/finally: 330
try/catch/finally: 373
No try/catch/finally: 329
try/catch/finally: 373
No try/catch/finally: 330
try/catch/finally: 373
No try/catch/finally: 352
try/catch/finally: 374
No try/catch/finally: 331
try/catch/finally: 380
No try/catch/finally: 329
try/catch/finally: 374
No try/catch/finally: 334
我试了一个深接球。
static void TryCatch(int level, int max)
{
try
{
if (level < max) TryCatch(level + 1, max);
}
catch
{ }
}
static void NoTryCatch(int level, int max)
{
if (level < max) NoTryCatch(level + 1, max);
}
static void Main(string[] args)
{
var s = new Stopwatch();
const int max = 10000;
s.Start();
TryCatch(0, max);
s.Stop();
Console.WriteLine("try-catch " + s.Elapsed);
s.Restart();
NoTryCatch(0, max);
s.Stop();
Console.WriteLine("no try-catch " + s.Elapsed);
}
结果:
try-catch 00:00:00.0008528
no try-catch 00:00:00.0002422