零成本异常处理与setjmp/longjmp

18
假设设置恢复点需要一定成本,那么可以优化类似这样的循环:
while (doContinue) {
   try {
     doSomeWork ();
   }
   catch (...) {}
}

转换成这样的形式:

while (doContinue) {
   try {
      do {
        doSomeWork ();
      } while (doContinue);
      break;
   } catch (...) {}
}

但如果平台支持零成本异常处理,这种优化就毫无意义。

有人能指出不同架构上如何实现零成本异常处理吗?是否有一种方法可以确定编译器/代码生成器可用的基础机制,以在编译时决定是否像此示例中那样进行优化。例如,如果编译器可以假设doSomeWork()与循环相关的副作用不存在,那么它是否可以为您进行优化?


1
零成本异常处理是否可能?我不知道,但对我来说似乎编译还没有完全优化。 - 9dan
你可以使用http://llvm.org/demo/index.cgi来查看两种解决方案的“合理”汇编代码。 - Matthieu M.
4
@9dan:是的,完全可以。我已经在Mac OS X上使用gcc 4.6从C++代码片段生成了汇编代码,并且通过查看汇编代码,你可以看到与“try-catch”块相关的任何开销都不存在。抛出/捕获异常时总会有开销,但如果这些异常情况不发生,那么运行时就没有任何成本。 - user405725
你在使用额外指令 Vlad 时看不到它,但是当使用 C 编译时,你可以看到一些优化被禁用了。这种开销很小(在实际生活中几乎无法测量),但确实存在。 - Lothar
2个回答

14

仅当目标可用时,才能使用零成本方法。如果可用,则由大多数生产质量的C ++编译器使用。否则,编译器将使用 setjmp / longjmp 方法。

setjmp / longjmp 的执行速度较慢。

然而,即使使用 setjmp / longjmp 方法,使用异常机制也可能比检查每个函数的返回代码(例如,在问题中的双循环优化示例中)导致更高的性能。

找出目标是否支持零成本方法以及编译器是否正在使用它的唯一方法是将C ++代码转换为汇编代码并进行分析。另一个解决方案是使用 gnat 调用 --RTS = zcx 并检查错误(如果 gnat 可用)。但这并不能保证C ++编译器会使用它。

因此,通常情况下,如果程序大小不是问题,并且零成本异常可用,则使用异常处理意外情况要比检查每个函数的返回代码好得多。否则,异常可以在某些情况下用于优化代码。

使用,但不滥用!

P.S.:我最终写了一篇article


我无法从你的回答或文章中看出最佳方法是什么。如果目标允许(并且编译器使用)零成本方法,那么这是否意味着在运行时绝对没有成本,因此不应使用原始的双循环优化,因为使用单个循环就可以了? - Steve Lorimer
@lori:很抱歉这不是一篇“深入”的文章(我不擅长写作)。如果您实际上抛出异常,会有一个与帧展开相关的成本,还有一个糟糕的代码局部性和大量的代码大小(throw 生成大量的代码,小函数)。使用 throw 的函数通常不会被内联,如果它在头文件中出现很多次,那么您的二进制文件大小将呈指数级增长。我认为必须限制使用异常的情况。例如,它们用于实现 NPTL 线程的取消。然而,将其用作控制流是无意义的。 - user405725
如果期望很少抛出异常,并且编译器/目标使用零成本方法,则在未抛出异常的情况下,所有成本都会用更大的代码空间支付,但在运行时没有任何影响。 - Steve Lorimer
1
@lori:如果代码大小较大,且未放置在特殊的“冷”部分(默认情况下不会发生),则会对性能产生影响。这是一个明显的影响。至少,确实会抛出异常的函数应该被分离并且不应内联。在阅读了Itanium ABI规范、重构了大量代码并看到性能提升后,我倾向于根本不使用异常。只要有可能。 - user405725
1
文章的链接已经失效。 - Pharap

7
我认为你对“零成本”估计过高。在此LLVM文档中,主要的影响是异常和上下文处理代码在编译时构建,因此在执行期间正常情况下没有额外的开销,成为一种时空权衡。在你的例子中,我相信会生成两倍数量的“降落垫”,增加大小并减慢异常处理速度。

4
我的意思是运行时成本为零。不仅LLVM,GCC也是这样,我相信其他编译器也是如此,在支持它的平台上生成编译时处理。因此,在进入try块时没有设置恢复点。如果是这种情况,我的优化将消除在循环的每次迭代中设置恢复点的操作。然而,由于成本为零,在循环外添加了大约两到三条指令。这并不会使运行时间变慢。但是让我困扰的是如何确定C++编译器将使用setjmp/longjmp还是在编译时生成处理代码。 - user405725

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接