所以,我知道try/catch会增加一些开销,因此不是控制流程流的好方法,但是这种开销来自何处以及它的实际影响是什么?
要点三点:
首先,在代码中实际使用try-catch块几乎没有或没有性能损失.在尝试避免将它们放入您的应用程序时,这不应该是一个考虑因素.只有在抛出异常时,性能才会发挥作用.
当异常,除了栈展开该采取哪些其他人所说的地点操作等被抛出,你应该知道的运行时间/反射相关的东西一大堆发生在以填充异常类的成员,如堆栈跟踪对象和各种类型的成员等
我相信这是为什么一般建议,如果你要重新抛出异常的原因之一是,throw;
而不是再次抛出异常或构造一个新异常,因为在这些情况下所有的堆栈信息都被重新调整而在简单中把它全部保留下来.
我不是语言实现方面的专家(所以请稍等一下),但我认为最大的成本之一是展开堆栈并将其存储为堆栈跟踪.我怀疑只有当抛出异常时才会发生这种情况(但我不知道),如果是这样的话,每次抛出异常时这都会有相当大的隐藏成本......所以它不像你只是从一个地方跳过在另一个代码中,有很多事情要发生.
只要您使用EXCEPTIONAL行为的异常(因此不是典型的,预期的程序路径),我认为这不是问题.
您是否询问在未抛出异常时使用try/catch/finally的开销,或者使用异常来控制流程的开销?后者有点类似于使用一根炸药点燃幼儿的生日蜡烛,相关的开销分为以下几个方面:
由于抛出的异常访问缓存中通常不存在的常驻数据,因此可能会出现额外的缓存未命中.
由于抛出的异常访问非常驻代码和数据通常不在应用程序的工作集中,因此可能会出现其他页面错误.
例如,抛出异常将要求CLR根据当前IP和每帧的返回IP找到finally和catch块的位置,直到处理异常加上过滤器块为止.
额外的建设成本和名称解析,以便为诊断目的创建框架,包括读取元数据等.
上述两个项目通常都会访问"冷"代码和数据,因此如果您有内存压力,则很可能出现硬页面错误:
CLR试图将不经常使用的代码和数据放在远离频繁使用的数据以改善局部性的地方,所以这对你不利,因为你迫使感冒变热.
硬页面错误的成本(如果有的话)会使其他所有内容相形见绌.
典型的捕获情况通常很深,因此上述影响往往会被放大(增加页面错误的可能性).
至于成本的实际影响,这可能会有很大差异,具体取决于当时代码中的其他内容.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(); }
当测试数据库充分利用测试数据时,这会在打开新表格等时导致非常明显的减速.
因此,我将其重构为以下内容,根据后来的快速测量结果,它大约快2个数量级:
bool HasRight(string rightName, DomainObject obj) { return _user.Rights.Contains(rightName); } void CheckRight(string rightName, DomainObject obj) { if (!HasRight(rightName, obj)) throw new Exception(); }
因此,简而言之,在正常流程中使用异常比使用类似的流程流程慢两个数量级,没有例外.