[英]Does C++ compiler optimize virtual member calls?
我正在考慮創建一個新的大型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();
}
};
聽到您的想法和經驗會很高興。
達斯·月亮
C ++編譯器是否優化虛擬成員調用?
是的,如果編譯器可以在編譯時確定具體類型,則它可能可以取消虛擬函數調用的虛擬化。
不可以,C ++編譯器將無法取消所有虛擬函數調用的虛擬化。
虛擬功能較慢。 大約10%
假設10%的差異是正確的,請考慮函數調用開銷可能在幾納秒的數量級。 幾納秒的10%並不多。 您可以在像游戲這樣的軟實時仿真的單個迭代中容納許多納秒。
編譯器不能看到我的窗口嗎?
那么編譯器是否能夠優化我的可預測虛擬函數調用?
也許。
首先, 必須在分配指針的上下文中內聯擴展run
調用。 否則,它不能對指向的對象做任何假設。 為了進行內聯擴展,必須在與調用該函數的位置相同的轉換單元中對其進行定義(LTO可能能夠解除此要求)。
此外,編譯器必須能夠證明在執行過程中的任何時候都沒有修改window
以指向另一個對象。 取決於循環的樣子,這種證明可能是不可能的,但是有一種簡單的方法可以簡化這一過程:聲明指針const。
至於您的編譯器是否進行了優化……我不知道。 但是您的編譯器可以,所以我建議您直接向您的編譯器提問(即要求它編譯程序並查看其作用)。
讓我們總結一下我們的評論。
虛擬調用的成本很高,但是如果處理器能夠檢測到模式,則由於現代處理器內部的預測器,虛擬調用的成本會降低。
現在,讓我們檢查您的代碼:
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
的調用將通過IWindow
進行的虛擬run
。
現在有了lto
(鏈接時間優化),編譯器也許可以找出並消除代碼的虛擬化,但是可能不會,因為優化選項的數量會隨着文件數量以及組合數量的增加而增加。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.