编译器(使用链接时优化)在处理快速返回的函数(提前输出路径)方面做得如何? [英] How well do compilers (with link-time optimization) cope with functions that return quickly (early-out paths)?

查看:39
本文介绍了编译器(使用链接时优化)在处理快速返回的函数(提前输出路径)方面做得如何?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在C中,如果我有一个如下所示的函数调用

// main.c
...
do_work_on_object(object, arg1, arg2);
...

// object.c
void do_work_on_object(struct object_t *object, int arg1, int arg2)
{
  if(object == NULL)
  {
    return;
  }
  // do lots of work
}

然后编译器将在main.o中生成大量内容来保存状态、传递参数(在本例中希望是在寄存器中)和恢复状态。

然而,在链接时可以观察到,在快速返回路径中不使用arg1和arg2,因此可以短路清理和状态恢复。链接器是否倾向于自动执行此类操作,或者是否需要打开链接时间优化(LTO)才能执行此类操作?

(是的,我可以检查反汇编代码,但我对编译器和链接器的总体行为以及在多个体系结构上的行为感兴趣,因此希望学习他人的经验。)

假设性能分析显示此函数调用值得优化,我们是否应该预期以下代码会显著更快(例如,无需使用LTO)?

// main.c
...
if(object != NULL)
{
  do_work_on_object(object, arg1, arg2);
}
...

// object.c
void do_work_on_object(struct object_t *object, int arg1, int arg2)
{
  assert(object != NULL) // generates no code in release build
  // do lots of work
}

推荐答案

某些编译器(如GCC和Clang)能够进行";收缩包装";优化,以将保存调用保留的regs延迟到可能的早期退出之后(如果它们能够发现该模式)。但有些则不是,例如apparently MSVC 16.11 still doesn't

我不认为有任何部分内联只是提前签入调用方,以避免arg传递和调用/ret本身的开销。


由于编译器/链接器对此的支持并不是通用的,甚至对于收缩包装也不总是成功的,因此您可以以一种获得更多好处的方式编写代码,代价是将函数的逻辑拆分为两个位置。

如果您有一个几乎不需要任何代码的快速路径,但发生的频率足够高,因此很重要,请将该部分放在一个头中,以便它被内联,并回退到调用函数的其余部分(您将其设置为私有的,因此它可以假设内联部分中的所有检查已经完成)。

例如,当Galois16因子为零时,PAR2处理数据块的例程有一条快速路径。(dst[i] += 0 * src[i]是无运算,即使*是Galois16中的乘法,而+=是GF16加法(即按位XOR))。

注意the commit in question如何将旧函数重命名为InternalProcess,并添加一个新的template<class g> inline bool ReedSolomon<g>::Process来检查快速路径,否则将调用InternalProcess。(以及进行一系列不相关的空格更改,以及一些ifdefs...最初是2006年的CVS提交。)

提交中的注释声称修复速度总体提高8%。

这篇关于编译器(使用链接时优化)在处理快速返回的函数(提前输出路径)方面做得如何?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆