连接器如何应对快速返回的功能?

时间:2015-04-17 17:47:39

标签: c linker micro-optimization lto

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

2 个答案:

答案 0 :(得分:2)

由于编译器/链接器对此的支持并不普遍,因此您可以以一种获得大部分好处的方式编写代码,但代价是将函数的逻辑拆分为两个位置。

如果你有一个几乎不需要任何代码的快速路径,但经常发生这种情况,那就把它放在一个标题中,这样它就会被内联,然后调用函数的其余部分(你私有的,所以它可以假设内联部分中的任何检查已经完成。)

e.g。 par2处理数据块的例程具有galois16因子为零时的快速路径。 (dst[i] += 0 * src[i]是无操作,即使*是Galois16中的乘法,+=是GF16加法(即按位异或))。

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

提交中的评论声称修复速度总体提高了8%。

答案 1 :(得分:0)

设置或清理状态代码都不能短路,因为生成的编译代码是静态的,并且它不知道程序执行时会发生什么。因此编译器将始终必须设置整个参数堆栈。

考虑两种情况:一个objectnil,另一个不是。汇编代码如何知道是否将其余参数放在堆栈上?特别是因为调用者负责将参数放在适当的位置(堆栈或注册表)。