【问题标题】:GCC de-virtualization of simple class简单类的 GCC 去虚拟化
【发布时间】:2015-11-19 02:55:28
【问题描述】:

以下代码不会被 gcc 去虚拟化。我可以做些什么来说服 gcc 去虚拟化?

struct B /* final */ {
    virtual int foo() { return 3; }
};

struct C {
    B& b;

    __attribute__((noinline))
    C( B& b ) : b(b) {
    }

    int foo() {
        return b.foo();
    }
};

int main() {
    B b;
    C c(b);

    int res = c.foo();
    return res;
}

我天真地认为这将被去虚拟化(至少是推测性的)并内联。

在构造函数是另一个编译单元的实际代码中,编译器将无法看到构造函数的主体(因此有 noinline 属性)。模仿一些现实世界的需求也不是最终的。

【问题讨论】:

  • 你目前使用什么编译标志?
  • -O3 -fdevirtualize -fdevirtualize-speculatively

标签: c++ gcc optimization vtable devirtualization


【解决方案1】:

当编译器在编译时知道对象的类型时,就会发生去虚拟化。在这里,您有 C::C 的 noinline,因此 main 无法知道在构造过程中实际最终到达 C::b 的对象类型。

【讨论】:

  • 如果是这种情况,那么推测性的去虚拟化将是不可能的。
  • 链接时间优化在现实世界的应用程序中提供性能改进的原因有很多。但即使没有链接时间优化,也会有一些微不足道的情况,例如堆栈分配的对象,然后是其虚函数调用。
【解决方案2】:

在构造函数是另一个编译单元的实际代码中,编译器将无法看到构造函数的主体(因此有 noinline 属性)。模仿一些现实世界的需求也不是最终的。

为了去虚拟化,编译器通常需要能够证明类层次结构是密封的。如果对构造函数的调用在单独的翻译单元中,编译器无法证明这一点。但是,使用link-time optimization 可以为优化器提供跨翻译单元的信息,从而更容易证明有关类层次结构和引用的事实。

这是一个使用 clang 的示例。

b.hpp

#ifndef B_H
#define B_H

struct B {

  virtual int foo();

};

#endif

b.cpp

#include "b.h"

int B::foo() { return 3; };

c.hpp

#ifndef C_H
#define C_H

#include "b.h"

struct C {

  B& b;

  C(B& b);

  int foo();

};

#endif

c.cpp

#include "c.h"

C::C(B& b) : b(b) {}

int C::foo() {

    return b.foo();
}

main.cpp

#include <iostream>

#include "b.h"
#include "c.h"

int main(const int argc, const char* argv[argc]) {

  B b;
  C c(b);

  std::cout << c.foo() << std::endl;

  return 0;
}

由于优化器对C::C 的调用站点一无所知( 构造函数)它对B 的运行时类型一无所知。所以,不能 去虚拟化B::foo

C::foo

_ZN1C3fooEv:                            # @_ZN1C3fooEv
    .cfi_startproc
# BB#0:
    movq    (%rdi), %rdi
    movq    (%rdi), %rax
    jmpq    *(%rax)                 # TAILCALL  <== pointer call

但是,给优化器链接时间 信息 (-flto) 允许它证明类层次结构与调用站点隔离。

B::foo

0000000000400960 <_ZN1B3fooEv>:
  400960:   b8 03 00 00 00          mov    $0x3,%eax
  400965:   c3                      retq   
  400966:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40096d:   00 00 00 

主要

0000000000400970 <main>:
  400970:   41 56                   push   %r14
  400972:   53                      push   %rbx
  400973:   50                      push   %rax
  400974:   48 c7 04 24 78 0a 40    movq   $0x400a78,(%rsp)
  40097b:   00 
  40097c:   48 8d 3c 24             lea    (%rsp),%rdi
  400980:   e8 db ff ff ff          callq  400960 <_ZN1B3fooEv> # <== direct call

【讨论】:

  • 我指的是 C 的构造函数。
  • @Kojo 我添加了一个示例,希望可以使其更清晰。
  • 仅供参考,如果该函数未调用 main ,则会发生去虚拟化。显然 main 是特殊的,gcc 知道它被调用一次,所以它总是针对大小进行优化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-23
  • 1970-01-01
  • 1970-01-01
  • 2018-08-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-17
相关资源
最近更新 更多