编辑11/20/2009:
我刚刚读了这篇有关改善托管代码性能的MSDN文章,这一部分让我想起了这个问题:
引发异常的性能代价是巨大的。尽管建议使用结构化异常处理方式来处理错误情况,但请确保仅在发生错误情况的特殊情况下才使用例外。不要为常规控制流使用异常。
当然,这仅适用于.NET,它还专门针对那些正在开发高性能应用程序的人(例如我本人)。所以这显然不是普遍真理。尽管如此,我们还有很多.NET开发人员,所以我觉得值得一提。
编辑:
好吧,首先,让我们直接讲一件事:我无意在性能问题上与任何人打架。总的来说,实际上,我倾向于同意那些认为过早优化是一种罪过的人。但是,我只想指出两点:
张贴者要求在传统观念基础上提出客观理由,即应该谨慎使用例外。我们可以讨论我们想要的可读性和适当的设计;但是这些都是主观的问题,人们随时准备在任何一方争论。我认为发布者已经意识到了这一点。事实是,使用异常来控制程序流通常是一种低效的处理方式。不,并非总是如此,但经常如此。这就是为什么谨慎使用例外情况是合理的建议的原因,就像少量食用红肉或喝红酒的良好建议一样。
在没有充分理由的情况下进行优化与编写高效代码之间存在区别。必然的结果是,编写健壮的东西(如果没有进行优化)和纯粹无效的东西是有区别的。有时候,我认为当人们争论诸如异常处理之类的事情时,他们实际上只是在互相交谈,因为他们正在讨论根本不同的事情。
为了说明我的观点,请考虑以下C#代码示例。
示例1:检测无效的用户输入
这就是所谓的异常滥用的一个例子。
int value = -1;
string input = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
value = int.Parse(input);
inputChecksOut = true;
} catch (FormatException) {
input = GetInput();
}
}
对我来说,这段代码很荒谬。当然可以。没有人对此争论。但这应该是这样的:
int value = -1;
string input = GetInput();
while (!int.TryParse(input, out value)) {
input = GetInput();
}
示例2:检查文件是否存在
我认为这种情况实际上非常普遍。对于许多人来说,这当然似乎更“可接受”,因为它处理文件I / O:
string text = null;
string path = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
using (FileStream fs = new FileStream(path, FileMode.Open)) {
using (StreamReader sr = new StreamReader(fs)) {
text = sr.ReadToEnd();
}
}
inputChecksOut = true;
} catch (FileNotFoundException) {
path = GetInput();
}
}
这似乎足够合理,对吧?我们正在尝试打开文件;如果不存在,我们将捕获该异常并尝试打开其他文件...这是怎么回事?
真的没什么。但是考虑一下这种替代方案,它不会引发任何异常:
string text = null;
string path = GetInput();
while (!File.Exists(path)) path = GetInput();
using (FileStream fs = new FileStream(path, FileMode.Open)) {
using (StreamReader sr = new StreamReader(fs)) {
text = sr.ReadToEnd();
}
}
当然,如果这两种方法的性能实际上相同,那么这实际上将纯粹是一个理论问题。因此,让我们看一下。对于第一个代码示例,我列出了10000个随机字符串的列表,其中没有一个表示正确的整数,然后在最后添加一个有效的整数字符串。使用以上两种方法,这些都是我的结果:
使用try
/catch
封锁:25.455秒
使用int.TryParse
:1.637毫秒
对于第二个示例,我基本上执行了相同的操作:列出了10000个随机字符串的列表,这些都不是有效路径,然后在最后添加一个有效路径。结果是:
使用try
/catch
阻止:29.989秒
使用File.Exists
:22.820毫秒
许多人对此表示反对:“是的,抛出并捕获10,000个异常是极其不现实的;这会夸大结果。” 当然可以。用户不会注意到抛出一个异常和自行处理错误输入之间的区别。事实仍然是,在这两种情况下,使用异常的速度比可读性高的替代方法慢1000到10,000倍以上(如果不是更多的话)。
这就是为什么我包含GetNine()
以下方法的示例。不是说它的速度慢得令人无法忍受或令人无法接受。这是它的速度慢于应有的速度……没有充分的理由。
同样,这些只是两个示例。中当然会有时候使用异常的性能损失并不严重这(帕维尔的权利;毕竟,它不依赖于实现)。我要说的是:伙计们,让我们面对现实-在上述情况下,抛出和捕获异常类似于GetNine()
;这只是做某事的效率低下,可以轻松地做得更好。
您要提出一个基本原理,就好像这是每个人都不知道为什么而跳入潮流的情况之一。但实际上答案很明显,我想您已经知道了。异常处理具有可怕的性能。
好的,也许对您的特定业务场景来说很好,但是相对而言,抛出/捕获异常会带来很多不必要的开销。您知道,我知道:大多数时候,如果您使用异常来控制程序流,您只是在编写慢速代码。
您可能还会问:为什么这段代码不好?
private int GetNine() {
for (int i = 0; i < 10; i++) {
if (i == 9) return i;
}
}
我敢打赌,如果您对此功能进行了概要分析,您会发现它对于典型的业务应用程序的执行速度相当快。这并没有改变这样一个事实,那就是这是一种效率低下的方法,无法完成可以做得更好的事情。
这就是人们谈论异常“滥用”时的意思。