我知道异常会对性能造成损失,并且尝试避免异常通常比放弃一切大尝试/捕获更有效 - 但是try块本身呢?仅仅声明try/catch的成本是多少,即使它从不抛出异常?
JSB*_*ոգչ 27
尝试的性能成本非常小.异常处理的主要成本是获取堆栈跟踪和其他元数据,这是在您实际必须抛出异常之前未支付的成本.
但这取决于语言和实施方式.为什么不在C#中编写一个简单的循环并自己计时呢?
实际上,几个月前我正在创建一个ASP.NET Web应用程序,我不小心用一个很长的循环包装了一个try/catch块.即使循环没有产生每个例外,但是花费了太多时间来完成.当我回去看到循环包裹的try/catch时,我反过来做了,我将循环包装在try/catch块中.性能提升很多.您可以自己尝试:做类似的事情
int total;
DateTime startTime = DateTime.Now;
for(int i = 0; i < 20000; i++)
{
try
{
total += i;
}
catch
{
// nothing to catch;
}
}
Console.Write((DateTime.Now - startTime).ToString());
Run Code Online (Sandbox Code Playgroud)
然后取出try/catch块.你会看到很大的不同!
一个常见的说法是,当它们被捕获时异常是昂贵的 - 而不是被抛出.这是因为大多数异常元数据收集(例如获取堆栈跟踪等)仅在try-catch端(不在throw端)发生.
展开堆栈实际上非常快 - CLR走上调用堆栈,只关注它找到的finally块; 在纯try-finally块中,运行时没有尝试"完成"异常(它的元数据等).
根据我的记忆,任何带过滤器的try-catches(例如"catch(FooException){}")都同样昂贵 - 即使它们没有对异常做任何事情.
我冒昧地用以下块说一个方法(称之为CatchesAndRethrows):
try
{
ThrowsAnException();
}
catch
{
throw;
}
Run Code Online (Sandbox Code Playgroud)
可能会导致方法中的堆栈遍历更快 - 例如:
try
{
CatchesAndRethrows();
}
catch (Exception ex) // The runtime has already done most of the work.
{
// Some fancy logic
}
Run Code Online (Sandbox Code Playgroud)
一些数字:
With: 0.13905ms
Without: 0.096ms
Percent difference: 144%
Run Code Online (Sandbox Code Playgroud)
这是我运行的基准测试(记住,发布模式 - 没有调试运行):
static void Main(string[] args)
{
Stopwatch withCatch = new Stopwatch();
Stopwatch withoutCatch = new Stopwatch();
int iterations = 20000;
for (int i = 0; i < iterations; i++)
{
if (i % 100 == 0)
{
Console.Write("{0}%", 100 * i / iterations);
Console.CursorLeft = 0;
Console.CursorTop = 0;
}
CatchIt(withCatch, withoutCatch);
}
Console.WriteLine("With: {0}ms", ((float)(withCatch.ElapsedMilliseconds)) / iterations);
Console.WriteLine("Without: {0}ms", ((float)(withoutCatch.ElapsedMilliseconds)) / iterations);
Console.WriteLine("Percent difference: {0}%", 100 * withCatch.ElapsedMilliseconds / withoutCatch.ElapsedMilliseconds);
Console.ReadKey(true);
}
static void CatchIt(Stopwatch withCatch, Stopwatch withoutCatch)
{
withCatch.Start();
try
{
FinallyIt(withoutCatch);
}
catch
{
}
withCatch.Stop();
}
static void FinallyIt(Stopwatch withoutCatch)
{
try
{
withoutCatch.Start();
ThrowIt(withoutCatch);
}
finally
{
withoutCatch.Stop();
}
}
private static void ThrowIt(Stopwatch withoutCatch)
{
throw new NotImplementedException();
}
Run Code Online (Sandbox Code Playgroud)
要查看其实际成本,可以运行以下代码。它采用简单的二维数组并生成超出范围的随机坐标。如果您的例外仅发生一次,那么您当然不会注意到它。我的示例旨在强调执行数千次操作的含义,以及捕获异常与实施简单测试可以节省您的时间。
const int size = 1000;
const int maxSteps = 100000;
var randomSeed = (int)(DateTime.UtcNow - new DateTime(1970,1,1,0,0,0).ToLocalTime()).TotalMilliseconds;
var random = new Random(randomSeed);
var numOutOfRange = 0;
var grid = new int[size,size];
var stopwatch = new Stopwatch();
Console.WriteLine("---Start test with exception---");
stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < maxSteps; i++)
{
int coord = random.Next(0, size * 2);
try
{
grid[coord, coord] = 1;
}
catch (IndexOutOfRangeException)
{
numOutOfRange++;
}
}
stopwatch.Stop();
Console.WriteLine("Time used: " + stopwatch.ElapsedMilliseconds + "ms, Number out of range: " + numOutOfRange);
Console.WriteLine("---End test with exception---");
random = new Random(randomSeed);
stopwatch.Reset();
Console.WriteLine("---Start test without exception---");
numOutOfRange = 0;
stopwatch.Start();
for (int i = 0; i < maxSteps; i++)
{
int coord = random.Next(0, size * 2);
if (coord >= grid.GetLength(0) || coord >= grid.GetLength(1))
{
numOutOfRange++;
continue;
}
grid[coord, coord] = 1;
}
stopwatch.Stop();
Console.WriteLine("Time used: " + stopwatch.ElapsedMilliseconds + "ms, Number out of range: " + numOutOfRange);
Console.WriteLine("---End test without exception---");
Console.ReadLine();
Run Code Online (Sandbox Code Playgroud)
此代码的示例输出:
---Start test with exception---
Time used: 3228ms, Number out of range: 49795
---End test with exception---
---Start test without exception---
Time used: 3ms, Number out of range: 49795
---End test without exception---
Run Code Online (Sandbox Code Playgroud)