C 链接器如何处理快速返回的函数?

C 链接器如何处理快速返回的函数?,c,linker,micro-optimization,lto,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中生成大量内容来保存状态、传递参数

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


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


考虑两种情况:在一种情况下,object为
nil
,在另一种情况下,object为not。汇编代码如何知道是否将参数的其余部分放在堆栈上?特别是调用方负责将参数放置在其正确的位置(堆栈或注册表)。

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

如果您有一条几乎不需要任何代码的快速路径,但经常发生的情况非常重要,请将该部分放在头中,使其成为内联的,并返回到调用函数的其余部分(您将其设为私有,以便它可以假定内联部分中的任何检查都已完成)

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

请注意如何将旧函数重命名为
InternalProcess
,并添加新的
模板内联bool ReedSolomon::Process
,以检查快速路径,否则将调用
InternalProcess
。(以及进行一系列不相关的空白更改,以及一些
ifdef
…最初是2006年的CVS提交。)


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

这种优化的术语是“收缩包装”。在生产编译器中实现它异常困难;我只知道有人甚至试图在特殊情况下,比如vtable thunks和PLT stub,这样做。我可能已经过时了。为什么要投否决票?这是一个有趣的问题。安德鲁·亨勒(Andrew Henle)这些都是真的,但快速返回案例正是我所问的。收缩包装绝对是一件众所周知的事情,例如,1998年的纸张设置确实不能短路,因为函数中的条件尚未执行。但是清理可能会短路,因为链接器可以对退出点的机器状态和清理代码之后的状态进行推理。没有必要(说)在实际工作没有发生的路径上,弹出寄存器会被实际工作污染。我的问题是链接器是否倾向于这样做。调用方所做的任何初始化(例如,推送参数)实际上只能在编译器能够将早期返回一直提升到调用方的情况下被跳过,这更像是一种LTO操作,而不是链接器松弛操作。然而,原则上,链接器(或者更可能是编译器)可以很好地移动初始
testarg\u reg\u 0;如果_为零,则返回_说明高于被调用方的所有开场白设置工作。令我惊讶的是,GCC4.9可以做到这一点。(clang 3.5不能)(我很惊讶,因为这正是我认为“在生产编译器中实现起来异常困难”的地方。也许我只是低估了GCC最终消除臭名昭著的“重新加载”过程的程度,这意味着过去很难的事情已经不复存在了。)我们应该在此基础上运行一些基准测试,由于缺少序言/尾声,性能提升是显而易见的。LLVM指出,这是他们希望改进的一个方面:
// 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
}