【问题标题】:Will GCC optimize away an inline accessor?GCC 会优化掉内联访问器吗?
【发布时间】:2020-05-20 18:58:15
【问题描述】:

假设我有这门课

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;

-O2-O3 上的 GCC 是否会优化对 x() 的任何调用而只获得 v[0]?即:

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

或者这是不可能的原因?

更新:应该提一下,我确实意识到 inline 更多的是对编译器的建议,但无论如何我都想知道。

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

【问题讨论】:

  • 扩展@Slava 的评论:在类体内完全定义的函数自动成为inline 函数(或者如果两者之间有任何区别,我不知道)
  • @TylerShellberg inline 不一定会导致函数被内联。这只是一个提示;如果函数很大,一个合理的编译器无论如何都不会内联它。
  • @TylerShellberg 在class 定义中定义的方法被隐式标记为inline,这是从 C++98 开始的。如果你愿意,你可以让它明确,但这不会做任何改变。它类似于将virtual 用于子类,您可以明确说明但不会改变任何内容
  • @HolyBlackCat 严格来说它只是优化器的一个热门,但从语言的角度来看差异很大 - 它允许函数/方法的多个定义。
  • 完整的定义必须在类中才能成为inline。声明是不行的。如上所述,编译器可能有其他计划。请参阅As If Rule:如果您无法区分,编译器可以对代码执行任何 操作。

标签: c++ c++11 gcc optimization accessor


【解决方案1】:

GCC 会优化掉内联访问器吗?

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

或者这是不可能的原因?

没有理由让它不可能,但也没有保证。

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

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

【讨论】:

    【解决方案2】:

    它可能是内联的,也可能不是内联的。那里没有保证。但是,如果您希望它始终被内联,请使用 [[gnu::always_inline]] 属性。请参阅文档here。仅当您知道自己在做什么时才使用此属性。在大多数情况下,最好让编译器决定哪些优化是合适的。

    【讨论】:

    • 假设我比编译器更聪明通常是不明智的。在这种情况下,是否有理由强制这些始终内联可能会使性能变差? IE,也许是一些奇怪的缓存大小问题或什么?
    • @TylerShellberg 是的,如果你内联太多,你会降低性能。这就是编译器并不总是这样做的原因。
    • @TylerShellberg 在这么小的函数中,不太可能。但总的来说,没有理由强制内联。只需让编译器进行优化即可。
    【解决方案3】:

    您可以在此处观察差异:https://godbolt.org/

    假设您有此代码(您的无法编译:缺少;Point 没有[]):

    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 emits:

    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
    

    我对汇编程序的阅读不是那么精通,但我认为将上面的内容与the output with -O3 进行比较就足够有说服力了:

    main:
            mov     eax, 10
            ret
    

    生成的代码可能会因上下文而有很大差异,我只是想知道是否有任何根本原因它永远不会或总是会发生。

    上面的例子已经反驳了“从不”。然而,“总是”很难得到。您得到的保证是,生成的代码的行为就像编译器在没有应用优化的情况下翻译了您的代码一样。除了少数例外,通常不能保证优化。确实,我只会依靠在现实场景中查看编译器的输出。

    【讨论】:

    • 优化器在这种情况下确定了整个程序的行为!这就是您可能会从int main() { return 10; } 看到的输出。
    • 我认为 OP 会问他是否可以总的来说。如果是这种情况,那么特定代码的汇编程序输出是无用的,没有分析。
    • @ashepler 确定,但要达到这一点,编译器还必须优化对函数的调用;)
    • @Slava 是的,添加了一条注释。一个例子就足以说明存在可以应用优化的情况,我想了一个反例,但没有找到
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-09
    • 2011-08-26
    • 1970-01-01
    • 2021-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多