C# 中 try/catch 的真正开销是多少?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/52312/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 10:05:40  来源:igfitidea点击:

What is the real overhead of try/catch in C#?

提问by JC Grubbs

So, I know that try/catch does add some overhead and therefore isn't a good way of controlling process flow, but where does this overhead come from and what is it's actual impact?

所以,我知道 try/catch 确实会增加一些开销,因此不是控制流程的好方法,但是这种开销从何而来,它的实际影响是什么?

采纳答案by Mike Stone

I'm not an expert in language implementations (so take this with a grain of salt), but I think one of the biggest costs is unwinding the stack and storing it for the stack trace. I suspect this happens only when the exception is thrown (but I don't know), and if so, this would be decently sized hidden cost every time an exception is thrown... so it's not like you are just jumping from one place in the code to another, there is a lot going on.

我不是语言实现方面的专家(所以对此持保留态度),但我认为最大的成本之一是展开堆栈并将其存储以用于堆栈跟踪。我怀疑只有在抛出异常时才会发生这种情况(但我不知道),如果是这样,每次抛出异常时,这都会是相当大的隐藏成本......所以这不像你只是从一个地方跳下来在另一个代码中,有很多事情要做。

I don't think it's a problem as long as you are using exceptions for EXCEPTIONAL behavior (so not your typical, expected path through the program).

只要您对异常行为使用异常(所以不是您典型的、预期的程序路径),我认为这不是问题。

回答by Tobi

In my experience the biggest overhead is in actually throwing an exception and handling it. I once worked on a project where code similar to the following was used to check if someone had a right to edit some object. This HasRight() method was used everywhere in the presentation layer, and was often called for 100s of objects.

根据我的经验,最大的开销是实际抛出异常并处理它。我曾经参与过一个项目,其中使用类似于以下的代码来检查某人是否有权编辑某个对象。这个 HasRight() 方法在表示层的任何地方都被使用,并且经常被数百个对象调用。

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();
}

When the test database got fuller with test data, this lead to a very visible slowdown while openening new forms etc.

当测试数据库中的测试数据变得更满时,这会导致在打开新表单等时出现非常明显的减速。

So I refactored it to the following, which - according to later quick 'n dirty measurements - is about 2 orders of magnitude faster:

所以我将它重构为以下内容,根据后来的快速 'n 脏测量 - 速度提高了大约 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();
}

So in short, using exceptions in normal process flow is about two orders of magnitude slower then using similar process flow without exceptions.

所以简而言之,在正常流程中使用异常比使用类似流程而没有异常要慢两个数量级。

回答by Hafthor

I made a blog entryabout this subject last year. Check it out. Bottom line is that there is almost no cost for a try block if no exception occurs - and on my laptop, an exception was about 36μs. That might be less than you expected, but keep in mind that those results where on a shallow stack. Also, first exceptions are really slow.

去年我写了一篇关于这个主题的博客。一探究竟。最重要的是,如果没有发生异常,try 块几乎没有成本 - 在我的笔记本电脑上,异常大约为 36 微秒。这可能比您预期的要少,但请记住,这些结果位于浅堆栈中。此外,第一个例外真的很慢。

回答by Shaun Austin

Three points to make here:

这里要说明三点:

  • Firstly, there is little or NO performance penalty in actually having try-catch blocks in your code. This should not be a consideration when trying to avoid having them in your application. The performance hit only comes into play when an exception is thrown.

  • When an exception is thrown in addition to the stack unwinding operations etc that take place which others have mentioned you should be aware that a whole bunch of runtime/reflection related stuff happens in order to populate the members of the exception class such as the stack trace object and the various type members etc.

  • I believe that this is one of the reasons why the general advice if you are going to rethrow the exception is to just throw;rather than throw the exception again or construct a new one as in those cases all of that stack information is regathered whereas in the simple throw it is all preserved.

  • 首先,在代码中实际使用 try-catch 块几乎没有或没有性能损失。当试图避免在您的应用程序中使用它们时,这不应该是一个考虑因素。只有在抛出异常时,性能才会发挥作用。

  • 除了其他人提到的堆栈展开操作等之外,当抛出异常时,您应该意识到会发生一大堆运行时/反射相关的事情,以便填充异常类的成员,例如堆栈跟踪对象和各种类型的成员等。

  • 我相信这是为什么如果您要重新抛出异常的一般建议是throw;不要再次抛出异常或构造一个新异常,因为在这些情况下所有堆栈信息都被重新收集,而在简单的情况下,这就是原因之一扔了就全保存了。

回答by dotmad

Not to mention if it's inside a frequently-called method it may affect the overall behavior of the application.
For example, I consider the use of Int32.Parse as a bad practice in most cases since it throws exceptions for something that can be caught easily otherwise.

更不用说它是否在经常调用的方法中,它可能会影响应用程序的整体行为。
例如,我认为在大多数情况下使用 Int32.Parse 是一种不好的做法,因为它会抛出异常,否则很容易被捕获。

So to conclude everything written here:
1) Use try..catch blocks to catch unexpected errors - almost no performance penalty.
2) Don't use exceptions for excepted errors if you can avoid it.

所以总结一下这里写的所有内容:
1) 使用 try..catch 块来捕获意外错误 - 几乎没有性能损失。
2) 如果可以避免,不要对异常错误使用异常。

回答by HTTP 410

Are you asking about the overhead of using try/catch/finally when exceptions aren't thrown, or the overhead of using exceptions to control process flow? The latter is somewhat akin to using a stick of dynamite to light a toddler's birthday candle, and the associated overhead falls into the following areas:

您是在询问未抛出异常时使用 try/catch/finally 的开销,还是使用异常控制流程的开销?后者有点类似于使用炸药棒点燃幼儿的生日蜡烛,相关的开销分为以下几个方面:

  • You can expect additional cache misses due to the thrown exception accessing resident data not normally in the cache.
  • You can expect additional page faults due to the thrown exception accessing non-resident code and data not normally in your application's working set.

    • for example, throwing the exception will require the CLR to find the location of the finally and catch blocks based on the current IP and the return IP of every frame until the exception is handled plus the filter block.
    • additional construction cost and name resolution in order to create the frames for diagnostic purposes, including reading of metadata etc.
    • both of the above items typically access "cold" code and data, so hard page faults are probable if you have memory pressure at all:

      • the CLR tries to put code and data that is used infrequently far from data that is used frequently to improve locality, so this works against you because you're forcing the cold to be hot.
      • the cost of the hard page faults, if any, will dwarf everything else.
  • Typical catch situations are often deep, therefore the above effects would tend to be magnified (increasing the likelihood of page faults).
  • 由于抛出的异常访问了通常不在缓存中的驻留数据,您可能会出现额外的缓存未命中。
  • 由于抛出的异常访问了应用程序工作集中通常不存在的非驻留代码和数据,因此您可能会出现额外的页面错误。

    • 例如,抛出异常将需要 CLR 根据当前 IP 和每帧的返回 IP 找到 finally 和 catch 块的位置,直到处理异常加上过滤器块。
    • 额外的构建成本和名称解析,以创建用于诊断目的的帧,包括读取元数据等。
    • 上述两项通常访问“冷”代码和数据,因此如果您有内存压力,则可能会出现硬页面错误:

      • CLR 试图将不经常使用的代码和数据与经常使用的数据远离以改善局部性,因此这对您不利,因为您迫使寒冷变热。
      • 硬页面错误的成本(如果有的话)将使其他一切相形见绌。
  • 典型的捕获情况通常很深,因此上述影响往往会被放大(增加页面错误的可能性)。

As for the actual impact of the cost, this can vary a lot depending on what else is going on in your code at the time. Jon Skeet has a good summary here, with some useful links. I tend to agree with his statement that if you get to the point where exceptions are significantly hurting your performance, you have problems in terms of your use of exceptions beyond just the performance.

至于成本的实际影响,这可能会因当时代码中发生的其他事情而有很大差异。Jon Skeet在这里有一个很好的总结,还有一些有用的链接。我倾向于同意他的说法,如果您到了异常严重损害您的性能的地步,那么除了性能之外,您在使用异常方面会遇到问题。

回答by HTTP 410

It is vastly easier to write, debug, and maintain code that is free of compiler error messages, code-analysis warning messages, and routine accepted exceptions (particularly exceptions that are thrown in one place and accepted in another). Because it is easier, the code will on average be better written and less buggy.

编写、调试和维护没有编译器错误消息、代码分析警告消息和例行接受的异常(特别是在一个地方抛出并在另一个地方接受的异常)的代码要容易得多。因为它更容易,平均而言,代码会写得更好,错误更少。

To me, that programmer and quality overhead is the primary argument against using try-catch for process flow.

对我来说,程序员和质量开销是反对使用 try-catch 进行流程的主要论据。

The computer overhead of exceptions is insignificant in comparison, and usually tiny in terms of the application's ability to meet real-world performance requirements.

相比之下,异常的计算机开销微不足道,而且就应用程序满足现实世界性能要求的能力而言,通常很小。

回答by HTTP 410

I really like Hafthor's blog post, and to add my two cents to this discussion, I'd like to say that, it's always been easy for me to have the DATA LAYER throw only one type of exception (DataAccessException). This way my BUSINESS LAYER knows what exception to expect and catches it. Then depending on further business rules (i.e. if my business object participates in the workflow etc), I may throw a new exception (BusinessObjectException) or proceed without re/throwing.

我真的很喜欢 Hafthor 的博客文章,并在此讨论中添加我的两分钱,我想说的是,让数据层只抛出一种类型的异常 (DataAccessException) 对我来说总是很容易。这样我的业务层就知道期望什么异常并捕获它。然后根据进一步的业务规则(即,如果我的业务对象参与工作流等),我可能会抛出一个新的异常 (BusinessObjectException) 或继续进行而不重新/抛出。

I'd say don't hesitate to use try..catch whenever it is necessary and use it wisely!

我会说,在必要时不要犹豫使用 try..catch 并明智地使用它!

For example, this method participates in a workflow...

例如,此方法参与工作流...

Comments?

注释?

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;
}

回答by BlackWasp

I wrote an article about this a while back because there were a lot of people asking about this at the time. You can find it and the test code at http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx.

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

The upshot is that there is a tiny amount of overhead for a try/catch block but so small that it should be ignored. However, if you are running try/catch blocks in loops that are executed millions of times, you may want to consider moving the block to outside of the loop if possible.

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

The key performance issue with try/catch blocks is when you actually catch an exception. This can add a noticeable delay to your application. Of course, when things are going wrong, most developers (and a lot of users) recognise the pause as an exception that is about to happen! The key here is not to use exception handling for normal operations. As the name suggests, they are exceptional and you should do everything you can to avoid them being thrown. You should not use them as part of the expected flow of a program that is functioning correctly.

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

回答by Vando

We can read in Programming Languages Pragmatics by Michael L. Scott that the nowadays compilers do not add any overhead in common case, this means, when no exceptions occurs. So every work is made in compile time. But when an exception is thrown in run-time, compiler needs to perform a binary search to find the correct exception and this will happen for every new throw that you made.

我们可以在 Michael L. Scott 的 Programming Languages Pragmatics 中读到,现在的编译器在普通情况下不会增加任何开销,这意味着当没有异常发生时。所以每项工作都是在编译时完成的。但是当在运行时抛出异常时,编译器需要执行二进制搜索以找到正确的异常,并且每次新抛出的异常都会发生这种情况。

But exceptions are exceptions and this cost is perfectly acceptable. If you try to do Exception Handling without exceptions and use return error codes instead, probably you will need a if statement for every subroutine and this will incur in a really real time overhead. You know a if statement is converted to a few assembly instructions, that will performed every time you enter in your sub-routines.

但例外就是例外,这个成本是完全可以接受的。如果您尝试在没有异常的情况下进行异常处理并使用返回错误代码,则可能您需要为每个子例程使用 if 语句,这将导致真正的实时开销。您知道 if 语句被转换为一些汇编指令,每次您进入子程序时都会执行这些指令。

Sorry about my English, hope that it helps you. This information is based on cited book, for more information refer to Chapter 8.5 Exception Handling.

对不起我的英语,希望它可以帮助你。此信息基于引用的书籍,有关更多信息,请参阅第 8.5 章异常处理。