编译器是否能够将 try/catch 优化为简单的 goto?

发布于 2024-09-11 08:03:51 字数 643 浏览 1 评论 0原文

在我看来,如果你有一些像这样的 C++ 代码:

int f()
{
  try {
    if( do_it() != success ) {
      throw do_it_failure();
    }
  } catch( const std::exception &e ) {
    show_error( e.what() );
  }
}

C++ 编译器应该能够将 throw 和 catch 优化为几乎简单的 goto。

然而,根据我查看反汇编和单步执行代码的经验,编译器总是跳过非常混乱的异常处理库。

他们为什么这么做?是否存在某些阻碍优化的语言要求?如果是的话怎么办:

int f()
{
  try { throw std::runtime_error("Boo!"); }
  catch ( const std::exception &e ) { std::cout << e.what() << std::endl; }
}

为什么编译器不只是将其重写为

int f()
{
  std::cout << "Boo!" << std::endl;
}

It seems to me that if you have some C++ code like this:

int f()
{
  try {
    if( do_it() != success ) {
      throw do_it_failure();
    }
  } catch( const std::exception &e ) {
    show_error( e.what() );
  }
}

The C++ compiler should be able to optimize the throw and catch into almost a simple goto.

However, it seems to me from my experience viewing disassembly and stepping through code that the compilers always jump through the very messy exception handling libraries.

Why do they do that? Is there some language requirement that prevents optimizing? What if it was:

int f()
{
  try { throw std::runtime_error("Boo!"); }
  catch ( const std::exception &e ) { std::cout << e.what() << std::endl; }
}

Why does the compiler not just rewrite that as

int f()
{
  std::cout << "Boo!" << std::endl;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

无畏 2024-09-18 08:03:51

我认为所接受的答案即使没有错误,也是相当无用的,所以即使过了这么多年,我仍然觉得有必要提供一个正确的答案。

猜测为什么编译器实现者选择不在任何特定功能上投入精力只是,好吧......猜测。仅在特殊情况下抛出异常这一事实通常不会被认为是不优化此类代码性能的理由。相反,尽管抛出代码确实没有以牺牲非抛出代码为代价进行优化,但异常抛出和处理基础设施仍然经过非常仔细的优化。

此外,这段代码可能感觉很做作,不值得考虑,但事实并非如此:它可能是由于内联和优化更复杂的代码而产生的,优化它可能会产生更简单的代码,从而允许其他优化过程启动,或包含要进一步内联的函数。像这样的优化过程,当正确且高效地实现时,总是值得至少被考虑,无论原始代码看起来多么做作。否则,即使是像死代码消除这样的基本步骤也会被避免,因为“死代码不应该首先编写”。显然情况并非如此。

因此,我只是不同意已接受的答案。事实上,应该异常抛出异常并不是此代码未优化的原因。

原因纯粹是技术性的,clang 开发邮件列表中的这封电子邮件对此进行了解释: http://lists.llvm.org/pipermail/cfe-dev/2015-March/042035.html

总而言之,该语言允许在 catch 块内调用代码在“从未见过”异常对象的任何点重新抛出异常:

void g() { throw; }

因此,考虑OP代码:

int f()
{
  try { throw std::runtime_error("Boo!"); }
  catch ( const std::exception &e ) { std::cout << e.what() << std::endl; }
}

对于编译器所关心的,e.what() ,或者两次调用operator<<,可能会重新抛出异常,因此优化异常处理代码会破坏程序的语义。

确保情况并非如此,需要“整个程序知识”,如上面的电子邮件中所写。更简单的情况可以进行优化,例如:

int func() {
  try {
    throw 42;
  }catch(int x) {
    return x;
  }
}

上面的代码可以转化为return 42。没有任何技术原因阻碍它。

尽管如此,大多数常见的编译器都不会这样做(godbolt)。这次,我们可以从实际来源(上面链接的电子邮件)看出,Clang 开发人员(我们不能对其他编译器说什么)认为这种优化不值得,可能,因为它只适用捕获不执行函数调用的块。

不管怎样,该消息没有说明他们是否会接受补丁来实现这一点。

I think the accepted answer is quite uninformative if not wrong, so even after so many years I feel the need to offer a proper answer.

Speculating on why the compiler implementors chose to not put effort on any particular feature is just, well... speculation. The fact that exceptions are thrown only in exceptional circumstances is not generally felt as a reason to not optimize the performance of such code. On the contrary, even though it is true that throwing code is not optimized at the expense of non-throwing code, the exception throwing and handling infrastructure is nevertheless optimized very carefully.

Furthermore, that piece of code might feel so contrived that is not worth considering, but this is not true: it may result from inlining and optimizing of much more complex code, and optimizing it could result into simpler code that allows other optimization passes to fire, or the containing function to be further inlined. Optimization passes like these, when correct and efficient to implement, are always worth of at least being considered, no matter how contrived the original piece of code might look. Otherwise, even fundamental passes like dead code elimination would be avoided because "dead code should not be written in the first place". Which is obviously not the case.

Hence I just do not agree with the accepted answer. The fact that exceptions should be thrown exceptionally is not the reason why this code is not optimized.

The reason is purely technical, and is explained in this email from the clang development mailing list: http://lists.llvm.org/pipermail/cfe-dev/2015-March/042035.html

To summarize, the language allows code called inside the catch block to rethrow the exception in any point "without ever having seen" the exception object:

void g() { throw; }

Hence, consider the OP code:

int f()
{
  try { throw std::runtime_error("Boo!"); }
  catch ( const std::exception &e ) { std::cout << e.what() << std::endl; }
}

For what the compiler is concerned, e.what(), or the two invocations of operator<<, may rethrow the exception, hence optimizing away the exception handling code would break the semantics of the program.

Ensuring this is not the case would require "whole-program knowledge", as written in the above email message. Even simpler cases could be optimized, such as:

int func() {
  try {
    throw 42;
  }catch(int x) {
    return x;
  }
}

The above code can be transformed into return 42. There are no technical reasons that impede it.

Still, most common compilers do not do it (godbolt). This time, we can tell from an actual source, the email linked above, that Clang developers (we cannot say anything for other compilers) do not think this optimization to be worth, probably because it would only apply to catch blocks that do not do function calls.

Anyway, the message says nothing about whether they would accept a patch to do so.

谜兔 2024-09-18 08:03:51

因为 do_it() 可能会在抛出 do_it_failure(); 之前抛出不同的异常

至于第二个示例,编译器可以执行此操作,但必须对其进行处理作为一个特例,何必去理会这种病态的情况呢?

Because do_it() could throw a different exception, before you throw do_it_failure();

As for your second example, a compiler could do it, but it would have to be treated as a special case, so why bother for such a pathological case?

余生共白头 2024-09-18 08:03:51

他们为什么要这么做?

因为 C++ 异常适用于异常情况,并且异常情况下的性能并不重要。

C++ 的异常在设计时就考虑到了这一点,确保编译器供应商能够在不引发异常的常见情况下提供接近最佳的性能,但代价是在引发异常的奇怪情况下性能低于可能的性能。

从一开始就鼓励用户仅在特殊情况下使用异常,并鼓励实现者优化无异常情况(析构函数地址必须存储在某处,以便在异常发生时调用析构函数),但以异常情况为代价。案例。
虽然实施者当然可以花费资源来优化奇怪的例外情况,但大多数用户不会喜欢这样,因为总是有很多更重要的事情需要改进。

Why do they do that?

Because C++ exception are for, well, exceptional circumstances and performance under exceptional circumstances doesn't really matter.

C++' exceptions were designed with that in mind, making sure compiler vendors could deliver near-optimal performance in the common case of no exceptions being thrown, at the cost of worse-than-possible performance in the odd case when exceptions are thrown.

From the very beginning users were encouraged to use exceptions only under exceptional circumstances, and implementers were encouraged to optimize the no-exception case (destructor addresses must be stored somewhere in order to call destructors when an exception comes by) at the cost of the exceptional case.
And while implementers could certainly spend resources at also optimizing the odd exceptional case, most users wouldn't like that, since there's always so much more important things that need improvment.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文