GCC是否会优化掉内联访问器?

3

假设我有这个类

class Point 
{
  inline float x() const { return v[0]; }
  inline float y() const { return v[1]; }
  inline float z() const { return v[2]; }

  float v[3];
};

我可以:

Point myPoint;
myPoint[0] = 5;

// unrelated code goes here

float myVal = myPoint.x() + 5;

GCC在使用-O2-O3优化选项时,是否会通过只获取v [0]来优化掉对函数x()的任何调用?也就是说:

float myVal = myPoint.v[0] + 5;

还是有某些无法完成的原因吗?

更新:我知道inline更多的是给编译器的建议,但还是想问一下。

另外一个问题是,将此类进行模板化是否会影响可以进行的优化效果?


3
扩展@Slava的评论:完全在类体内定义的函数会自动成为“内联”函数(如果两者之间有任何区别,我不知道)。 - user4581301
2
@TylerShellberg 在 class 定义内部定义的方法会被隐式标记为 inline,这是自 C++98 以来的规定。如果你愿意,你可以将其显式声明,但这并不会有任何改变。这与在子类中使用 virtual 类似,你可以将其显式声明,但这并不会改变任何东西。 - Slava
2
@HolyBlackCat 严格来说,这只是优化器的一个命中,但从语言角度来看,差异很大——它允许函数/方法的多个定义。 - Slava
2
请注意,C++中inline的实际现代含义并不是“提示优化器”,而是“此函数可以在多个翻译单元中定义,并且将始终在使用它的翻译单元中定义”。由头文件定义的函数应该是inline,因为它们可能会被多次包含,另一部分告诉编译器它不一定需要为链接器单独生成定义,因为如果它总是选择内联它,定义将始终可见。 - aschepler
3
完整的定义必须在类内部才能使用 inline。仅仅声明是不够的。正如上面所指出的,编译器可能有其他计划。请参考仿佛规则:如果你无法察觉到任何差别,编译器可以对代码进行任何它想要的更改。 - user4581301
显示剩余12条评论
3个回答

6

GCC是否会优化内联访问器?

所有的优化编译器都会这样做。与其他优化相比,这是一种微不足道的优化。

还是说这不可能呢?

没有任何理由使它不可能,但也没有保证。

另外一个问题,对这个类进行模板化是否会影响可以进行的优化?

不会。但是,当然,编译器可能会对模板有不同的内联阈值。


2

这段代码可能会被内联或者不被内联,没有保证。但是如果你想让它总是被内联,可以使用[[gnu::always_inline]]属性。在这里查看相关文档。只有当你知道自己在做什么时才使用该属性。在大多数情况下,最好让编译器决定哪些优化最适合。


通常假设我比编译器更聪明是不明智的。在这种情况下,强制使它们始终内联是否会导致性能变差?例如,可能存在一些奇怪的缓存大小问题或其他问题吗? - Tyler Shellberg
@TylerShellberg 是的,如果你过度使用内联函数,性能会变差。这就是为什么编译器并不总是这样做的原因。 - Acorn
@TylerShellberg 在这么小的一个函数中,这是不太可能的。但一般来说,没有必要强制内联。只需让编译器进行优化即可。 - Ayxan Haqverdili
1
@TylerShellberg https://isocpp.org/wiki/faq/inline-functions#inline-and-perf - François Andrieux

2
你可以在这里看到区别:https://godbolt.org/ 假设你有以下代码(你的代码无法编译:缺少 ;[]):
struct Point 
{
  inline float x() const { return v[0]; }
  inline float y() const { return v[1]; }
  inline float z() const { return v[2]; }

  float v[3];
};

int main() {
    Point myPoint;
    myPoint.v[0] = 5;

    float myVal = myPoint.x() + 5;
    return myVal;
}

然后gcc 9.2发布

Point::x() const:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        movss   xmm0, DWORD PTR [rax]
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        movss   xmm0, DWORD PTR .LC0[rip]
        movss   DWORD PTR [rbp-16], xmm0
        lea     rax, [rbp-16]
        mov     rdi, rax
        call    Point::x() const
        movss   xmm1, DWORD PTR .LC0[rip]
        addss   xmm0, xmm1
        movss   DWORD PTR [rbp-4], xmm0
        movss   xmm0, DWORD PTR [rbp-4]
        cvttss2si       eax, xmm0
        leave
        ret
.LC0:
        .long   1084227584

我在阅读汇编代码方面并不是很熟练,但我认为将上述内容与使用-O3输出结果进行比较 已经足够有说服力了:

main:
        mov     eax, 10
        ret

生成的代码可能会因上下文而有很大的变化,我只是想知道是否有任何根本原因它永远不会或总会发生。
上面的例子已经证明了“永远不会”的错误。然而,“总是”很难实现。您得到的保证是,生成的代码的行为就像编译器在未应用优化的情况下翻译您的代码一样。除了少数例外,通常不能保证优化。要确保,我只会依靠在现实情况下查看编译器输出。

1
优化器在这种情况下确定了整个程序的行为!这就是你可能会从int main() { return 10; }看到的输出。 - aschepler
1
我认为 OP 问的是他是否可以一般地依赖这样的事实。如果是这种情况,那么特定代码的汇编输出是无用的,不能进行分析。 - Slava
@ashepler 当然可以,但编译器还必须优化掉对函数的调用才能到达那个状态 ;) - 463035818_is_not_a_number
@Slava 是的,已经添加了一条注释。一个例子足以说明优化可以应用的情况,我试图想到一个反例,但没有找到。 - 463035818_is_not_a_number

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