Answers:
我不是语言实现方面的专家(因此请耐心等待),但是我认为最大的成本之一就是展开堆栈并将其存储以进行堆栈跟踪。我怀疑只有在抛出异常时才会发生这种情况(但我不知道),如果是这样,那么每次抛出异常时,这将是相当大的隐藏成本...因此,这不像您只是从一个地方跳下来在另一个代码中,发生了很多事情。
我不认为这是一个问题,只要您对异常行为使用异常(这样就不会在程序中使用通常的预期路径)。
这里要说明三点:
首先,在代码中实际包含try-catch块几乎没有性能损失。尝试避免将它们包含在您的应用程序中时,这不是考虑因素。仅在引发异常时才会影响性能。
当除了其他人提到的堆栈展开操作等引发异常时,您应该意识到,发生了一系列与运行时/反射相关的事情,以便填充异常类的成员,例如堆栈跟踪对象和各种类型的成员等。
我认为,这就是为什么要抛出异常的一般建议是只是throw;
而不是再次抛出异常或构造一个新的异常,原因是在这些情况下,所有堆栈信息都被收集,而在简单情况下,扔掉它全部保留下来。
throw new Exception("Wrapping layer’s error", ex);
您是否在询问没有抛出异常时使用try / catch / finally的开销,还是使用异常来控制流程的开销?后者有点类似于用炸药点燃幼儿的生日蜡烛,相关的开销落在以下区域:
由于引发的异常访问应用程序工作集中通常不访问的非驻留代码和数据,您可能会遇到其他页面错误。
以上两个项目通常都访问“冷”代码和数据,因此如果您完全有内存压力,则很可能发生硬页面错误:
至于成本的实际影响,这可能会变化很大,具体取决于当时代码中发生的其他事情。乔恩·斯基特(Jon Skeet)在这里有一个不错的摘要,并提供了一些有用的链接。我倾向于同意他的说法,即如果您到达了一个异常会严重影响您的性能的地步,那么您在使用异常方面就存在着性能之外的问题。
以我的经验,最大的开销是实际抛出异常并对其进行处理。我曾经在一个项目中工作过,该项目使用类似于以下代码的代码来检查某人是否有权编辑某些对象。HasRight()方法在表示层的任何地方都使用过,通常被称为100个对象。
bool HasRight(string rightName, DomainObject obj) {
try {
CheckRight(rightName, obj);
return true;
}
catch (Exception ex) {
return false;
}
}
void CheckRight(string rightName, DomainObject obj) {
if (!_user.Rights.Contains(rightName))
throw new Exception();
}
当测试数据库充满测试数据时,这会导致打开新表格等时的速度明显下降。
因此,我将其重构为以下内容-根据稍后的快速'n脏测量-大约快了两个数量级:
bool HasRight(string rightName, DomainObject obj) {
return _user.Rights.Contains(rightName);
}
void CheckRight(string rightName, DomainObject obj) {
if (!HasRight(rightName, obj))
throw new Exception();
}
简而言之,在正常流程中使用例外要比在没有例外的情况下使用类似流程慢两个数量级。
与普遍接受的理论相反,try
/ catch
可能会对性能产生重大影响,那就是是否引发异常!
前者已被覆盖由微软MVP的一对夫妇的博客文章多年来,我相信你可以很容易地找到他们又StackOverflow的关心这么多有关内容,所以我会提供链接到一些他们作为填充物的证据:
try
/ catch
/finally
(和第二部分),由彼得里奇探索的优化其try
/catch
/finally
禁用(我会再进入这个引号从标准)Parse
与TryParse
对比ConvertTo
由伊恩·赫夫公然指出“异常处理是非常缓慢”,并点蚀表明了这一点Int.Parse
,并Int.TryParse
互相...为了谁的人坚持认为,TryParse
使用try
/catch
在幕后,这应该提供一些线索!还有一个答案,显示了使用-和不使用try
/的反汇编代码之间的区别catch
。
它似乎很明显,有是一个开销,这是在代码生成公然观察到的,那开销甚至似乎是承认人谁微软的价值!但是我在重复互联网 ...
是的,对于一条微不足道的代码行,还有数十条额外的MSIL指令,而且甚至没有涵盖禁用的优化,因此从技术上讲,它是一种微优化。
几年前,我发布了一个答案,该答案被删除了,因为它专注于程序员的生产力(宏优化)。
不幸的是,由于此处没有节省几纳秒的时间,因此那里的CPU时间很可能会弥补人类手动优化积累的许多小时。您的老板为哪个支付更多:一个小时的时间,还是一个计算机运行一个小时?我们在什么时候拔下插头并承认是时候购买一台速度更快的计算机了?
显然,我们应该优化优先级,而不仅仅是代码!在我的最后一个答案中,我借鉴了两个代码片段之间的区别。
使用try
/ catch
:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
不使用try
/ catch
:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
从维护开发人员的角度考虑,如果不进行概要分析/优化(如上所述),这很可能会浪费您的时间(如果不是针对try
/ catch
问题,甚至没有必要),然后滚动浏览源代码...其中之一有四行样板垃圾!
随着越来越多的领域被引入一个类中,所有这些样板垃圾(源代码和反汇编代码中)的堆积都远远超出了合理的水平。每个字段多出四行,而且它们总是相同的行...我们不是在教我们避免重复吗?我想我们可以将try
/ catch
隐藏在一些自制的抽象后面,但是...那么我们最好也避免出现异常(即use Int.TryParse
)。
这甚至不是一个复杂的例子。我已经看到了在try
/ 中实例化新类的尝试catch
。考虑到构造函数内部的所有代码随后可能会失去某些优化的资格,否则这些优化将由编译器自动应用。有什么更好的方法可以使编译器运行缓慢,而不是完全按照编译器的指示运行呢?
假设所述构造函数引发异常,并因此触发了一些错误,则维护欠佳的开发人员必须对其进行跟踪。与goto噩梦的意大利面条代码不同,try
/ catch
可能会导致三个方面的混乱,因为它不仅可以移到同一方法的其他部分,而且还可以移入同一方法的其他部分,因此可能并非易事,所有这些都会由维护开发人员严格遵守!但是我们被告知“ goto很危险”,嘿!
最后,我提到了try
/ catch
的好处是,它旨在禁止优化!如果愿意的话,它是调试的辅助工具!这就是它的设计宗旨,应将其用作...
我想这也是一个积极的观点。它可以用来禁用可能会破坏多线程应用程序的安全,理智的消息传递算法的优化,并捕获可能的争用条件;)那是我想到的唯一尝试使用try / catch的方案。即使那样,也有其他选择。
什么做的优化try
,catch
并finally
禁用?
又名
怎么样try
,catch
和finally
作为调试工具有用吗?
它们是写障碍。这来自标准:
12.3.3.13 Try-catch语句
对于以下形式的语句stmt:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n
- 的明确赋值状态v之初试块相同的明确赋值状态v之初语句。
- 在catch-block-i开头的v的明确赋值状态(对于任何i)与在stmt开头的v的明确赋值状态相同。
- v(在且仅当)v明确分配在try块和每个catch-block-i端点上(对于从1到n的每个i),才确定在stmt端点处v的确定分配状态)。
换句话说,在每个try
语句的开头:
try
语句之前,对可见对象所做的所有分配必须完成,这需要一个线程锁作为开始,这对于调试竞争条件很有用!try
语句之前已明确分配给未使用的变量分配每个catch
陈述都有相似的故事。假设在您的try
语句(或其调用的构造函数或函数等)中分配了该无意义的变量(例如garbage=42;
),则编译器无法消除该语句,无论该语句与程序的可观察行为有多大关系。进入块之前,需要完成分配catch
。
对于它的价值,finally
讲述一个同样可耻的故事:
12.3.3.14 Try-finally语句
对于以下形式的try语句stmt:
try try-block finally finally-block
• try-block 开头的v的明确分配状态与stmt开头的v的明确分配状态相同。 • finally块开头的v的明确分配状态与stmt开头的v的明确分配状态相同。 •的明确赋值状态v在终点语句是明确赋值如果(且仅当)两种:○ v在结束点明确分配的试块 Ø v
在终点是明确赋值最后块 如果控制流转移(如转到语句)由内开始试块,并且外端部的try块,然后v也视为已明确对分配如果将v明确地指定在finally块的端点,则控制流传输。(这不是唯一的,如果-如果在此控制流传输中由于其他原因而明确指定了v,那么仍将其视为绝对指定的。)
12.3.3.15 Try-catch-finally语句
尝试 - 捕获 - 最终声明形式的确定分配分析:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n finally finally-block
仿佛语句是一个做尝试 - 最后语句包围的try - 赶上声明:
try { try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n } finally finally-block
不久前,我写了一篇有关此的文章,因为当时有很多人在问这个。您可以在http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx上找到它和测试代码。
结果是,try / catch块的开销很小,但是很小,应该忽略。但是,如果要在执行了数百万次的循环中运行try / catch块,则可能需要考虑将块移到循环外部。
try / catch块的关键性能问题是实际捕获异常时。这会给您的应用程序增加明显的延迟。当然,当出现问题时,大多数开发人员(以及很多用户)都将暂停视为即将发生的异常!这里的关键是不要在正常操作中使用异常处理。顾名思义,它们是特殊的,您应该尽一切可能避免它们被抛出。您不应将它们用作正常运行的程序的预期流程的一部分。
我去年在此主题上写了一篇博客文章。看看这个。最重要的是,如果没有异常发生,try块几乎没有成本-在我的笔记本电脑上,异常约为36μs。这可能比您预期的要少,但是请记住,这些结果在浅层堆栈中。另外,第一个例外真的很慢。
try
/ catch
太多了吗?与您的建议相反...我很乐意提出我所做的研究是错误的建议,但我需要阅读您的博客条目以了解其内容。
try-catch
block vs tryparse()
方法,但是概念是相同的。
编写,调试和维护没有编译器错误消息,代码分析警告消息和例行接受的异常(尤其是放在一个地方并在另一个地方接受的异常)的代码要容易得多。因为它更容易,所以平均而言,该代码将更好地编写,而错误则更少。
对我来说,程序员和质量开销是反对使用try-catch进行流程的主要理由。
相比之下,异常的计算机开销微不足道,并且就应用程序满足实际性能要求的能力而言,通常很少。
我真的很喜欢Hafthor的博客文章,在此讨论中加我两分钱,我想说的是,让DATA LAYER仅抛出一种异常(DataAccessException)对我来说总是很容易的。这样,我的业务层就知道要期待什么异常并捕获它。然后,根据其他业务规则(即,如果我的业务对象参与了工作流等),我可能会抛出新的异常(BusinessObjectException)或继续进行而无需重新/抛出。
我想说,只要有必要,就不要犹豫地使用try..catch并明智地使用它!
例如,此方法参与工作流程...
注释?
public bool DeleteGallery(int id)
{
try
{
using (var transaction = new DbTransactionManager())
{
try
{
transaction.BeginTransaction();
_galleryRepository.DeleteGallery(id, transaction);
_galleryRepository.DeletePictures(id, transaction);
FileManager.DeleteAll(id);
transaction.Commit();
}
catch (DataAccessException ex)
{
Logger.Log(ex);
transaction.Rollback();
throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
}
}
}
catch (DbTransactionException ex)
{
Logger.Log(ex);
throw new BusinessObjectException("Cannot delete gallery.", ex);
}
return true;
}
我们可以从Michael L. Scott的《 Programming Languages Pragmatics》一书中读到,当今的编译器在一般情况下不会增加任何开销,这意味着没有异常发生。因此,每项工作都是在编译时完成的。但是,当在运行时引发异常时,编译器需要执行二进制搜索以查找正确的异常,并且对于您进行的每个新抛出都会发生这种情况。
但是例外就是例外,这笔费用是完全可以接受的。如果您尝试在没有异常的情况下进行异常处理,而改用返回错误代码,则可能需要为每个子例程添加一个if语句,这将导致真正的实时开销。您知道if语句将转换为一些汇编指令,该指令将在您每次输入子例程时执行。
对不起,我的英语,希望对您有帮助。该信息基于引用的书,有关更多信息,请参见第8.5章“异常处理”。
让我们分析一下try / catch块在不需要使用的情况下可能最大的成本之一:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
这是没有try / catch的:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
不计算无关紧要的空格,您可能会注意到这两个等价的代码段的字节长度几乎完全相同。后者的缩进量减少了4个字节。那是一件坏事?
为了增加侮辱性伤害,学生决定循环,同时可以将输入解析为int。没有try / catch的解决方案可能是这样的:
while (int.TryParse(...))
{
...
}
但是,使用try / catch时看起来如何?
try {
for (;;)
{
x = int.Parse(...);
...
}
}
catch
{
...
}
尝试/捕获块是浪费缩进的神奇方法,我们仍然不知道它失败的原因!想象一下,当代码继续通过严重的逻辑缺陷执行代码,而不是因出现明显的异常错误而停止运行时,进行调试的人员的感受。尝试/捕获块是一个懒人的数据验证/清除工具。
较小的成本之一是try / catch块确实确实禁用了某些优化:http : //msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx。我想这也是一个积极的观点。它可以用来禁用可能会破坏多线程应用程序的安全,理智的消息传递算法的优化,并捕获可能的争用条件;)那是我想到的唯一尝试使用try / catch的方案。即使那样,也有其他选择。
Int.Parse
支持Int.TryParse
。