在C#中try / catch的实际开销是多少?

时间:2020-03-05 18:50:10  来源:igfitidea点击:

因此,我知道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();
}

当测试数据库充满测试数据时,这会导致打开新表格等时的速度明显下降。

因此,我将其重构为以下内容,根据稍后的" 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块几乎没有成本,并且在我的笔记本电脑上,异常发生时间约为36s。这可能比我们预期的要少,但是请记住,这些结果在浅层堆栈中出现。另外,第一个例外真的很慢。

回答

这里要说明三点:

  • 首先,在代码中实际包含try-catch块几乎没有性能损失。尝试避免将它们包含在应用程序中时,这不是考虑因素。仅在引发异常时才会影响性能。
  • 当除了其他人提到的堆栈展开操作等引发异常时,我们应该意识到,发生了一系列与运行时/反射相关的事情,以便填充异常类的成员,例如堆栈跟踪对象和各种类型的成员等。
  • 我认为,这就是为什么如果要抛出异常的一般建议是"抛出;"而不是再次抛出异常或者构造一个新的异常的原因之一,因为在这种情况下,所有堆栈信息都将被重新收集而在简单的抛出中,所有这些都保留了下来。

回答

更不用说它是否在常用方法内,它可能会影响应用程序的整体行为。
例如,在大多数情况下,我认为Int32.Parse的使用是一种不好的做法,因为它会引发某些异常,而这些异常本来可以很容易地捕获的。

因此,总结一下这里写的所有内容:
1)使用try..catch块捕获意外错误,几乎不会降低性能。
2)如果可以避免,请不要对例外错误使用例外。

回答

我们是否在询问没有抛出异常时使用try / catch / finally的开销,还是使用异常来控制流程的开销?后者类似于使用炸药点燃孩子的生日蜡烛,并且相关的开销落在以下区域:

  • 由于抛出的异常访问通常不在高速缓存中的常驻数据,我们可能会期望其他高速缓存未命中。
  • 为了创建用于诊断目的的框架(包括读取元数据等),需要额外的建造成本和名称解析。
  • 硬页面错误的成本(如果有的话)会使其他所有因素相形见.。
  • 典型的捕获情况通常很深,因此上述影响往往会被放大(增加页面错误的可能性)。

至于成本的实际影响,这可能会变化很大,具体取决于当时代码中发生的其他事情。乔恩·斯凯特(Jon Skeet)在这里有一个不错的摘要,并提供了一些有用的链接。我倾向于同意他的说法,即如果我们到达了一个异常会严重损害性能的地步,那么我们在使用异常方面就存在着性能方面的问题,而不仅仅是性能。

回答

编写,调试和维护没有编译器错误消息,代码分析警告消息和例行接受的异常(尤其是放在一个地方并在另一个地方接受的异常)的代码要容易得多。因为它更容易实现,所以平均而言,该代码将更好地编写,并且错误更少。

对我来说,程序员和质量开销是反对使用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;
}

回答

不久前,我写了一篇有关此的文章,因为当时有很多人在问这个。我们可以在http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx上找到它和测试代码。

结果是,try / catch块的开销很小,但是很小,应该忽略。但是,如果要在执行了数百万次的循环中运行try / catch块,则可能需要考虑将块移到循环外部。

try / catch块的关键性能问题是实际捕获异常时。这会给应用程序增加明显的延迟。当然,当出现问题时,大多数开发人员(以及很多用户)都将暂停视为即将发生的异常!这里的关键是不要在正常操作中使用异常处理。顾名思义,它们是特殊的,我们应该尽一切可能避免它们被抛出。我们不应将它们用作正常运行的程序的预期流程的一部分。

回答

我们可以从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的方案。即使那样,也有其他选择。