C ++编译器是否优化虚拟成员调用?

时间:2018-11-27 18:12:56

标签: c++ inheritance code-generation compiler-optimization virtual-functions

我正在考虑创建一个新的大型C ++项目。开始很容易-只是一个简单的窗口,也许是SDL2,也许SFML,甚至是WIN32。好吧,我该怎么办?使用我想要的任何窗口会不会更好?无需更改大量代码,以使其他类独立于此窗口?

说完了!使用一个简单的窗口界面,每个类都知道类似窗口的内容,并且我可以在不同类型之间进行选择。唯一的要求是将IWindow作为基类。

class IWindow {
  public:
    IWindow(std::string title, int posX, int posY, int width, int height);
    IWindow getHandle();
    void loop();
    bool toggleFullscreen();
    bool toggleFullscreen(bool fullscreen);
    int getWidth();
    int getHeight();
    int getPosX();
    int getPosY();
    //And so on ...
};

但是现在,由于我必须使用虚拟方法,因此每次我的虚拟函数循环都会被游戏循环调用。虚拟功能较慢。我读过大约10%。

编译器是否能够看到我的窗口?它来自哪种类型?难道看不到“ Jeah,这个程序员在此应用程序中创建了一个SDL窗口,因此只需在各处使用它的方法即可。”?我的意思是,我在主循环中定义了窗口,它永远不会改变。没什么动态的。这是可以预见的。

那么编译器是否可以优化我的可预测虚拟函数调用?这些将在每个游戏循环周期进行评估吗?像下面的段落一样?

int main(int argc, char* argv[]) {
  //Creates a window derived from IWindow
  SDL::Window myWindow("Title", 0, 0, 300, 100);
  //Storing it as IWindow in a wrapper class
  Game myGame(&myWindow);
  //Game loop
  //myGame.run() calls the window's loop
  while (myGame.run()) {
    //... doing game stuff
  }
}

具有这样的Game类:

class Game {
  protected:
    IWindow* window;
  public:
    bool run() {
      //Calls the window's virtual loop method.
      //Will it be optimized? Any way to do so?
      this->window->loop();
    }
};

很高兴听到您的想法和经验。

暗月

2 个答案:

答案 0 :(得分:2)

  

C ++编译器是否优化虚拟成员调用?

是的,如果编译器可以在编译时确定具体的类型,则它可能可以取消虚拟函数调用的虚拟化。

否,C ++编译器将无法取消所有虚拟函数调用的虚拟化。

  

虚拟功能较慢。大约10%

假设10%的差异是正确的,请考虑函数调用开销可能在几纳秒的数量级。几纳秒的10%并不多。您可以在像游戏这样的软实时仿真的一次迭代中适应许多纳秒的时间。

  

编译器是否能够看到我的窗口?

     

那么编译器是否能够优化我的可预测虚拟函数调用?

也许。

首先,在分配了指针的上下文中,必须内联扩展run 的调用。否则,它不能对指向的对象做任何假设。为了进行内联扩展,必须在与调用该函数的位置相同的转换单元中对其进行定义(但LTO可能可以解除此要求)。

此外,编译器必须能够证明window在执行过程中的任何时候都没有被修改以指向另一个对象。根据您的循环的样子,这种证明可能是不可能的,但是有一种简单的方法可以使它变得简单:声明指针const。

关于您的编译器是否优化了它……我不知道。但是您的编译器确实可以,所以我建议您直接向您的编译器提问(即要求它编译程序并查看其作用)。

答案 1 :(得分:0)

让我们总结一下我们的评论。

虚拟调用的成本很高,但是如果处理器能够检测到模式,则由于现代处理器内部的预测器,其调用成本将降低。

现在,让我们检查您的代码:

int main(int argc, char* argv[]) {
  //Creates a window derived from IWindow
  SDL::Window myWindow("Title", 0, 0, 300, 100);
  //Storing it as IWindow in a wrapper class
  Game myGame(&myWindow);
  //Game loop
  //myGame.run() calls the window's loop
  while (myGame.run()) {
    //... doing game stuff
  }
}

我们假设Game有一个虚拟的run。在这种情况下,编译器知道myGame的类型为Game,因此可以直接将调用放置到run函数中,而不必通过虚拟表。

现在,您在另一个文件中拥有此文件:

class Game {
  protected:
    IWindow* window;
  public:
    bool run() {
      //Calls the window's virtual loop method.
      //Will it be optimized? Any way to do so?
      this->window->loop();
    }
};

不幸的是,在这种情况下,编译器仅通过查看此文件就无法了解,因此对SDL::Window的调用将通过来自{{1 }}。

现在有了run(链接时间优化),编译器也许可以弄清楚它并对其进行虚拟化处理,但是随着优化选项的数量随文件数量的增加而增加,它可能无法解决。以及组合数量。