避免gcc功能序幕开销?

时间:2011-03-29 19:14:14

标签: c gcc code-generation x86

我最近遇到了很多函数,其中gcc在x86上生成了非常糟糕的代码。它们都符合以下模式:

if (some_condition) {
    /* do something really simple and return */
} else {
    /* something complex that needs lots of registers */
}

将简单的情况看作是一个非常小的东西,一半或更多的工作花在推动和弹出不会被修改的寄存器上。如果我手动编写asm,我会在复杂的情况下保存并恢复已保存的跨越调用寄存器,并且在简单的情况下完全避免触及堆栈指针。

有没有什么方法可以让gcc变得更聪明并自己动手?最好使用命令行选项,而不是源代码中的丑陋黑客...

编辑:为了使其具体,这里有一些非常接近我正在处理的一些功能:

if (buf->pos < buf->end) {
    return *buf->pos++;
} else {
    /* fill buffer */
}

另一个:

if (!initialized) {
    /* complex initialization procedure */
}
return &initialized_object;

和另一个:

if (mutex->type == SIMPLE) {
    return atomic_swap(&mutex->lock, 1);
} else {
    /* deal with ownership, etc. */
}

编辑2:我应该首先提到:这些函数无法内联。他们有外部链接,他们是图书馆代码。允许在应用程序中内联它们会导致各种问题。

5 个答案:

答案 0 :(得分:2)

<强>更新

要明确地抑制gcc中单个函数的内联,请使用:

void foo() __attribute__ ((noinline))
{
  ...
}

另见How can I tell gcc not to inline a function?


除非编译-O0(禁用优化),否则将自动内联这样的函数。

在C ++中,您可以使用内联关键字

提示编译器

如果编译器不接受您的提示,则可能在函数内部使用了太多的寄存器/分支。通过将“复杂”块提取到它自己的函数中,几乎可以肯定这种情况。


更新我注意到你添加了一个事实,即它们是外部符号。 (请用最重要的信息更新问题)。嗯,从某种意义上说,对于外部功能,所有的赌注都是关闭的。我真的不相信gcc会根据定义将所有复杂的函数内联到一个微小的调用者简单,因为它只是从那里调用。也许您可以提供一些演示行为的示例代码,我们可以找到适当的优化标志来解决这个问题吗?

,这是C还是C ++?在C ++中,我知道将内部简单的决策函数包含在内是很常见的(主要是在类声明中定义的成员)。这不会像简单(外部)C函数那样产生链接冲突。

此外,您可以定义模板函数,这些函数将在所有编译模块中完美地内联,而不会导致链接冲突。

我希望您使用的是C ++,因为它会为您提供大量选项。

答案 1 :(得分:2)

我会这样做:

static void complex_function() {}

void foo()
{
    if(simple_case) {
        // do whatever
        return;
    } else {
        complex_function();
    }
}

编译器我坚持内联complex_function(),在这种情况下你可以使用noinline属性。

答案 2 :(得分:1)

也许升级你的gcc版本? 4.6刚刚发布。据我了解,它有“部分内联”的可能性。也就是说,函数的易于集成的外部部分被内联并且昂贵的部分被转换成调用。但我必须承认,我自己并没有尝试过。

编辑:我在ChangeLog中引用的声明:

  

现在支持部分内联   默认情况下启用-O2和更高版本。   该功能可以通过控制   -fpartial-内联。

     

部分内联拆分功能   短暂的热门回归。这允许   更积极的内联热   路径领先以提高绩效   经常减少代码大小(因为   寒冷的部分功能不是   重复)。

     

...

     

优化大小时的内联   (无论是在一个程序的寒冷地区   或者用-Os编译时是   改进以更好地处理C ++程序   具有更大的抽象惩罚,   导致更小更快的代码。

答案 3 :(得分:0)

我可能会重构代码以鼓励内联简单案例。也就是说,您可以使用-finline-limit使gcc考虑内联更大的函数,或使用-fomit-frame-pointer -fno-exceptions来最小化堆栈帧。 (注意后者可能会破坏调试并导致C ++异常行为不当。)

但是,你可能无法从调整编译器选项中获得太多,并且必须重构。

答案 4 :(得分:0)

看到这些是外部调用,gcc可能会将它们视为不安全并保留函数调用的寄存器(很难知道它没有看到它保留的寄存器,包括那些你说'不使用的寄存器' “)。出于好奇,在禁用所有优化的情况下,这种过多的寄存器溢出是否仍会发生?