我倾向于使用异常,或者更准确地说,除非在正常使用时执行他们会变得太慢而不值得使用,否则我不会避免它们。我写了两篇短文文章来阐述这个观点。对基准测试方面的批评主要是“在现实生活中,会有更多的堆栈需要经过,因此你会清空缓存等”- 但使用错误代码来逐层处理堆栈也会使缓存失效,所以我不认为这是一个特别好的论据。
仅仅为了明确 - 我并不支持在不合适的情况下使用异常。例如,int.TryParse
完全适用于将用户的数据进行转换。当读取机器生成的文件时,如果失败意味着“该文件的格式不正确”,那么使用异常就是不合适的,因为我真的不想尝试处理它,因为我不知道还可能有什么其他问题。
在“仅仅合理的情况”下使用异常,我从未见过应用程序的性能因异常而受到显著影响。基本上,异常不应经常发生,除非你有重大的正确性问题,而如果你有重大的正确性问题,那么性能不是你面临的最大问题。
这个问题有一个权威的答案,来自实现它们的人 - Chris Brumme。他写了一篇优秀的博客文章,讲述了这个主题(警告-非常长)(警告2-写得非常好,如果你是技术人员,你会读到最后,然后不得不在下班后补回工作时间 :))
执行摘要:它们很慢。它们被实现为Win32 SEH异常,因此有些甚至会通过ring 0 CPU边界! 显然,在现实世界中,你会做很多其他工作,所以偶尔的异常根本不会被注意到,但如果你用它们来控制程序流程,那么你的应用程序将受到打击。这是微软营销机器给我们带来的另一个负面影响。我记得有一个微软员工告诉我们,他们完全没有任何开销,这完全是胡说八道。
Chris给出了一个相关的引用:
事实上,CLR甚至在未管理的引擎部分内部使用异常。但是,异常存在严重的长期性能问题,这必须考虑到您的决策中。
当人们说只有在抛出异常的情况下才会变慢时,我不知道他们在说什么。
编辑:如果没有抛出异常,则意味着您正在执行new Exception()或类似操作。否则,异常会导致线程被挂起,并且需要遍历堆栈。在小型情况下可能还可以接受,但在高流量网站中,依赖异常作为工作流或执行路径机制肯定会导致性能问题。异常本身并不是坏的,它们用于表示异常情况。
.NET应用程序中的异常工作流程使用第一次和第二次异常。对于所有异常情况,即使您捕获并处理它们,异常对象仍然会被创建,并且框架仍然必须遍历堆栈以查找处理程序。如果您捕获并重新抛出异常,这当然需要更长时间-您将获得第一次机会异常,捕获它,重新抛出它,导致另一个第一次机会异常,然后找不到处理程序,然后导致第二次机会异常。
异常也是堆上的对象 - 因此,如果您抛出大量异常,那么您会引起性能和内存问题。
此外,根据ACE团队编写的“性能测试Microsoft .NET Web应用程序”中的内容:
“异常处理很耗费资源。执行相关线程时,CLR会暂停,遍历调用堆栈以查找正确的异常处理程序,当它被找到时,异常处理程序和若干个finally块都必须有机会执行,然后才能执行常规处理。”
我在现场的经验表明,显著减少异常可以提高性能。当然,在性能测试时还有其他要考虑的因素 - 例如,如果您的磁盘I / O有问题或您的查询需要数秒钟,则应该将其作为重点。但是,查找并消除异常应该成为该策略的重要组成部分。
我尝试了一些方法来防止XMPP服务器出现性能问题(比如在尝试读取更多数据之前检查套接字是否已连接),并给自己提供了避免这些问题的途径(如TryX方法)。经过观察,我的XMPP服务器的速度得到了显著提升(抱歉,没有实际数字)。这是在只有约50个活跃用户(聊天)的情况下实现的。
我想补充一下我的最近经验:与上面大部分所写的一致,即使没有调试器运行,反复抛出异常也会非常慢。通过改变五行左右的代码,我切换到了返回码模型,将一个正在编写的大程序的性能提高了60%。当然,我更改之前的代码运行了数千次,并且在更改之前可能会抛出数千个异常。所以我同意上面的说法:只有在真正出现问题时才抛出异常,而不是作为控制应用程序流程的一种方式来处理“预期”的情况。
但是 Mono 抛出异常的速度比 .NET 独立模式快 10 倍, 而 .NET 独立模式抛出异常的速度比 .NET 调试器模式快 60 倍。 (测试机器使用相同的 CPU 型号)
int c = 1000000;
int s = Environment.TickCount;
for (int i = 0; i < c; i++)
{
try { throw new Exception(); }
catch { }
}
int d = Environment.TickCount - s;
Console.WriteLine(d + "ms / " + c + " exceptions");
baseline: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 13.0007 ms
baseline: recurse_depth 8, error_freqeuncy 0.25 (0), time elapsed 13.0007 ms
baseline: recurse_depth 8, error_freqeuncy 0.5 (0), time elapsed 13.0008 ms
baseline: recurse_depth 8, error_freqeuncy 0.75 (0), time elapsed 13.0008 ms
baseline: recurse_depth 8, error_freqeuncy 1 (0), time elapsed 14.0008 ms
retval_error: recurse_depth 5, error_freqeuncy 0 (0), time elapsed 13.0008 ms
retval_error: recurse_depth 5, error_freqeuncy 0.25 (249999), time elapsed 14.0008 ms
retval_error: recurse_depth 5, error_freqeuncy 0.5 (499999), time elapsed 16.0009 ms
retval_error: recurse_depth 5, error_freqeuncy 0.75 (999999), time elapsed 16.001 ms
retval_error: recurse_depth 5, error_freqeuncy 1 (999999), time elapsed 16.0009 ms
retval_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 20.0011 ms
retval_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 21.0012 ms
retval_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 24.0014 ms
retval_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 24.0014 ms
retval_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 24.0013 ms
exception_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 31.0017 ms
exception_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 5607.3208 ms
exception_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 11172.639 ms
exception_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 22297.2753 ms
exception_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 22102.2641 ms
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1 {
public class TestIt {
int value;
public class TestException : Exception { }
public int getValue() {
return value;
}
public void reset() {
value = 0;
}
public bool baseline_null(bool shouldfail, int recurse_depth) {
if (recurse_depth <= 0) {
return shouldfail;
} else {
return baseline_null(shouldfail,recurse_depth-1);
}
}
public bool retval_error(bool shouldfail, int recurse_depth) {
if (recurse_depth <= 0) {
if (shouldfail) {
return false;
} else {
return true;
}
} else {
bool nested_error = retval_error(shouldfail,recurse_depth-1);
if (nested_error) {
return true;
} else {
return false;
}
}
}
public void exception_error(bool shouldfail, int recurse_depth) {
if (recurse_depth <= 0) {
if (shouldfail) {
throw new TestException();
}
} else {
exception_error(shouldfail,recurse_depth-1);
}
}
public static void Main(String[] args) {
int i;
long l;
TestIt t = new TestIt();
int failures;
int ITERATION_COUNT = 1000000;
// (0) baseline null workload
for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {
int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);
failures = 0;
DateTime start_time = DateTime.Now;
t.reset();
for (i = 1; i < ITERATION_COUNT; i++) {
bool shoulderror = (i % EXCEPTION_MOD) == 0;
t.baseline_null(shoulderror,recurse_depth);
}
double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds;
Console.WriteLine(
String.Format(
"baseline: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms",
recurse_depth, exception_freq, failures,elapsed_time));
}
}
// (1) retval_error
for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {
int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);
failures = 0;
DateTime start_time = DateTime.Now;
t.reset();
for (i = 1; i < ITERATION_COUNT; i++) {
bool shoulderror = (i % EXCEPTION_MOD) == 0;
if (!t.retval_error(shoulderror,recurse_depth)) {
failures++;
}
}
double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds;
Console.WriteLine(
String.Format(
"retval_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms",
recurse_depth, exception_freq, failures,elapsed_time));
}
}
// (2) exception_error
for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {
int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);
failures = 0;
DateTime start_time = DateTime.Now;
t.reset();
for (i = 1; i < ITERATION_COUNT; i++) {
bool shoulderror = (i % EXCEPTION_MOD) == 0;
try {
t.exception_error(shoulderror,recurse_depth);
} catch (TestException e) {
failures++;
}
}
double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds;
Console.WriteLine(
String.Format(
"exception_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms",
recurse_depth, exception_freq, failures,elapsed_time)); }
}
}
}
}
如果将它们与返回代码进行比较,它们的速度非常慢。然而,正如之前的帖子所述,您不希望在正常程序操作中抛出异常,因此只有在出现问题时才会受到性能影响,在绝大多数情况下,性能已经不再重要(因为异常意味着阻碍)。
相对于错误代码,它们绝对值得使用,优点众多,我个人认为。