C ++异常抛出/捕获优化

时间:2010-07-22 17:31:52

标签: c++ exception optimization

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

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

C ++编译器应该能够优化throw并捕获几乎简单的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;
}

3 个答案:

答案 0 :(得分:6)

  

为什么他们这样做?

因为C ++异常是针对的,所以异常情况和特殊情况下的性能并不重要。

C ++'异常的设计考虑到了这一点,确保编译器供应商能够在没有异常被抛出的常见情况下提供接近最优的性能,代价是在异常情况下奇怪的情况下性能差于可能性。抛出。

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

答案 1 :(得分:5)

因为do_it()在抛出do_it_failure();

之前可能抛出不同的异常

至于你的第二个例子,编译器可以做到这一点,但它必须被视为特殊情况,那么为什么还要为这种病态案例而烦恼呢?

答案 2 :(得分:1)

我认为公认的答案即使没有错也没有多大意义,所以即使经过了这么多年,我仍然觉得有必要提供适当的答案。

猜测为什么编译器实现者选择不对任何特定功能进行努力。通常仅将异常仅在特殊情况下引发的事实视为不优化此类代码性能的原因。相反,即使确实没有以牺牲非抛出代码为代价优化投掷代码,但异常抛出和处理基础结构仍然经过了非常仔细的优化。

此外,这段代码可能感觉太虚构了,不值得考虑,但事实并非如此:这可能是由于内联和优化了更复杂的代码而导致的,而对其进行优化可能会导致生成更简单的代码,从而可以进行其他优化传递给火,或者包含函数进一步内联。像这样的优化过程,在正确有效地实现时,无论原始代码看起来有多精巧,始终至少应考虑它们的价值。否则,甚至可以避免诸如消除死代码之类的基本操作,因为“首先不应该编写死代码”。显然不是这样。

因此,我只是不同意接受的答案。 不是不是对该代码进行优化的原因。

原因纯粹是技术上的原因,此消息来自clang开发邮件列表:http://lists.llvm.org/pipermail/cfe-dev/2015-March/042035.html

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

void g() { throw; }

因此,请考虑以下操作代码:

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开发人员(我们无法对其他编译器说什么)不认为这种优化值得,因为它只会应用到不执行功能调用的catch块。

无论如何,该消息并没有说明他们是否愿意接受补丁。