C#中try/catch的实际开销是多少?
因此,我知道try/catch确实会增加一些开销,因此不是控制流程的好方法,但这种开销来自何处,它的实际影响是什么?我不是语言实现方面的专家(因此对此持保留态度),但我认为最大的成本之一是展开堆栈并将其存储以用于堆栈跟踪。我怀疑只有在抛出异常时才会发生这种情况(但我不知道),如果是这样的话,那么每次抛出异常时的隐藏成本都会相当大。。。所以这并不是说你只是从代码中的一个地方跳到另一个地方,有很多事情在进行C#中try/catch的实际开销是多少?,c#,.net,performance,optimization,try-catch,C#,.net,Performance,Optimization,Try Catch,因此,我知道try/catch确实会增加一些开销,因此不是控制流程的好方法,但这种开销来自何处,它的实际影响是什么?我不是语言实现方面的专家(因此对此持保留态度),但我认为最大的成本之一是展开堆栈并将其存储以用于堆栈跟踪。我怀疑只有在抛出异常时才会发生这种情况(但我不知道),如果是这样的话,那么每次抛出异常时的隐藏成本都会相当大。。。所以这并不是说你只是从代码中的一个地方跳到另一个地方,有很多事情在进行 我认为,只要您将异常用于异常行为(因此不是程序的典型预期路径),这就不是问题。根据我的经验,
我认为,只要您将异常用于异常行为(因此不是程序的典型预期路径),这就不是问题。根据我的经验,最大的开销实际上是抛出异常并处理它。我曾经参与过一个项目,其中使用类似于以下代码的代码来检查是否有人有权编辑某个对象。这个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();
}
当测试数据库中充满了测试数据时,在打开新表单时,这会导致非常明显的速度减慢
因此,我将其重构为以下内容,根据后来的quick'n dirty测量,其速度大约快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();
}
因此,简而言之,在正常流程中使用异常要比在没有异常的情况下使用类似流程慢两个数量级。我去年就这个问题做了一个讨论。
过来看。底线是,如果没有异常发生,一个try块几乎没有成本——在我的笔记本电脑上,一个异常大约是36μs。这可能比您预期的要少,但请记住,这些结果都是浅层堆栈。另外,第一个例外非常缓慢。这里要指出三点:
- 首先,在代码中实际使用try-catch块几乎没有或没有性能损失。当您试图避免在应用程序中使用它们时,不应考虑这一点。只有在抛出异常时,性能命中才会起作用
- 除了其他人提到的堆栈展开操作等之外,当抛出异常时,您应该知道,为了填充异常类的成员(如堆栈跟踪对象和各种类型成员等),会发生大量与运行时/反射相关的事情
- 我相信,这就是为什么要重新引用异常的一般建议是只
抛出异常的原因之一代码>而不是再次抛出异常或构造新异常,因为在这些情况下,所有堆栈信息都将重新存储,而在简单的抛出中,所有堆栈信息都将保留
例如,在大多数情况下,我认为使用It32.PARSE是一种错误的做法,因为它为其他容易被捕获的东西抛出异常。 因此,总结一下这里写的所有内容:
1) 使用try..catch块捕获意外错误-几乎没有性能损失
2) 如果可以避免,请不要对异常错误使用异常。您是在询问在不引发异常时使用try/catch/finally的开销,还是使用异常控制流程流的开销?后者在某种程度上类似于用一根炸药棒点燃一个蹒跚学步的孩子的生日蜡烛,其相关的开销分为以下几个方面:
- 由于访问缓存中通常不存在的常驻数据时引发异常,因此可能会导致额外的缓存未命中
- 由于抛出异常访问应用程序工作集中通常不存在的非驻留代码和数据,因此可能会出现额外的页面错误
- 例如,抛出异常将要求CLR根据当前IP和每个帧的返回IP查找finally和catch块的位置,直到异常处理完毕,再加上筛选器块
- 额外的构造成本和名称解析,以便创建用于诊断目的的框架,包括读取元数据等
- 上述两项通常都会访问“冷”代码和数据,因此,如果内存压力过大,则很可能出现硬页错误:
- CLR试图将不经常使用的代码和数据放在远离经常用于改进局部性的数据的位置,因此这对您不利,因为您正在迫使冷变热
- 硬页错误(如果有的话)的代价将使其他一切相形见绌
- 典型的捕获情况通常很深,因此上述影响可能会被放大(增加页面错误的可能性)
至于成本的实际影响,这可能会有很大的不同,这取决于当时代码中发生的其他事情。乔恩·斯基特有一本书,里面有一些有用的链接。我倾向于同意他的说法,即如果异常严重影响性能,那么除了性能之外,在使用异常方面存在问题。编写、调试和维护没有编译器错误消息、代码分析警告消息、,以及例行接受的异常(特别是在一个位置抛出并在另一个位置接受的异常)。因为它更简单,所以代码平均编写得更好,错误更少 对我来说,程序员和质量开销是反对对流程流使用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;
}
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
while (int.TryParse(...))
{
...
}
try {
for (;;)
{
x = int.Parse(...);
...
}
}
catch
{
...
}
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
try try-block
catch ( ... ) catch-block-1
...
catch ( ... ) catch-block-n
try try-block
finally finally-block
try try-block
catch ( ... ) catch-block-1
...
catch ( ... ) catch-block-n
finally finally-block
try {
try
try-block
catch ( ... ) catch-block-1
...
catch ( ... ) catch-block-n
}
finally finally-block