2010-05-13 79 views
1

只是把它弄出來的方式...速度比較 - 模板專業化與虛擬功能與if語句

Premature optimization is the root of all evil

Make use of OOP

etc.

我明白了。只是尋找一些關於某些操作的速度的建議,我可以將這些操作存儲在灰色物體中供將來參考。

假設你有一個動畫類。動畫可以循環播放(循環播放)或不循環(播放一次),它可以具有唯一的幀時間等等。假設有3個這樣的「任一」或「屬性」。 請注意,Animation類的任何方法至多會檢查其中的一個(即,這不是if-elseif的巨大分支的情況)。

這裏有一些選擇。

1)給它一個上面給出的屬性布爾成員,並使用if語句來打去執行相應的操作動畫時檢查對付他們。

  • 問題:有條件地檢查每次播放動畫。

2)做一個基礎的動畫類,並從中獲得其他動畫類,如LoopedAnimation和AnimationUniqueFrames等

  • 問題:在每次調用的V表檢查,以播放動畫因爲你碰到這樣的一個vector<Animation>。另外,爲所有可能的組合創建一個單獨的類看起來代碼笨拙。

3)使用模板專業化,並專門化那些依賴於這些屬性的函數。像template<bool looped, bool uniqueFrameTimes> class Animation

  • 問題:與此問題是,你不能只有一個vector<Animation>的東西的動畫。也可能會臃腫。

我想知道什麼樣的速度每個選項提供?我對第一和第二選項特別感興趣,因爲第三個選項不允許我們遍歷一個通用容器Animation s。

總之,什麼是快 - 一個虛函數表讀取或條件?

+4

嘗試自己測試一下。 – Blindy 2010-05-13 23:22:48

+1

這些屬性的實際值是在運行時還是在編譯時確定的?例如,如果您要加載用戶指定的文件,那麼屬性在運行時確定,當然。如果動畫是預先確定的,那麼這些值在編譯時已知。那麼,你在處理哪種情況? – AnT 2010-05-13 23:41:40

回答

4

(1)不使生成的彙編事項的大小了這些天,但是這是它產生(大約,在x86假設MSVC)什麼:

mov eax, [ecx+12] ; 'this' pointer stored in ecx, eax is scratch 
cmp eax, 0   ; test for 0 
jz .somewhereElse ; jump if the bool isn't set 

優化編譯器將穿插其他指令那裏,使它更加管道友好。無論如何,你的課程內容很可能會存放在你的緩存中,如果不是的話,無論如何它都需要幾個週期。所以,回想起來,這可能需要幾個週期,而對於每幀至多會被調用幾次的東西,這沒什麼。

(2)這大概是將每次你的play()方法被調用時產生的彙編:

mov eax, [ebp+4] ; pointer to your Animation* somewhere on the stack, eax is scratch 
mov eax, [eax+12] ; dereference the vtable 
call eax    ; call it 

然後,你就會有一些重複的代碼或你的專門的打內線另一個函數調用()函數,因爲它會定義一些常見的東西,所以會產生一些開銷(代碼大小和/或執行速度)。所以,這個速度肯定比較慢。

此外,這使加載通用動畫更加困難。你的圖形部門不會很高興。 (3)爲了有效地使用它,你最終會爲你的模板化版本創建一個基類,使用虛函數(在這種情況下,請參閱(2)),或者你可以通過檢查類型在你稱之爲動畫的地方,在這種情況下,請參閱(2)。

這也使加載泛型動畫變得更加困難。你的圖形部門會更加不快樂。 (4)你需要擔心的不是對於一個框架中幾次完成的微小事情的微觀優化。從閱讀你的文章,我確定了另一個常被忽視的問題。你提到std :: vector <動畫>。沒有什麼反對STL,但那是糟糕的巫術。一個單獨的內存分配會花費比循環播放()或更新()方法中的所有布爾檢查更多的週期,這可能是整個應用程序運行的時間。將動畫放入和退出std :: vectors(特別是如果您將實例放入實例而不是指向實例的指針(智能或非啞))將會花費更多。

你需要看看不同的地方進行優化。這是一種非常荒謬的微觀優化,除了使它更難概括並使你的圖形部門感到高興之外,它不會帶來任何好處。但是,重要的是擔心內存分配問題,然後,當您完成對該部分的編程後,啓動一個分析器並查找熱點位置。

如果讓你的動畫實際上成爲一個瓶頸,std :: vector(很好,因爲它是)你可能想看的地方。你有沒有看過一個侵入式鏈表?這實際上會比這個擔心更有益。

+0

只要您提前預留(),根據我的經驗,矢量都可以。 – leander 2010-05-13 23:47:35

+0

確實如此,但假設你事先知道你在那裏放了多少東西。儘管如此,你很少需要隨機訪問這樣的東西,因此侵入式版本仍然(有點)更好,因爲你不需要爲這些指針分配緩衝區 - 反正分配的對象已經有了。 – arke 2010-05-14 00:03:30

+0

噢,還有一個+1會讓人煩心出去拆解它=)我喜歡看編譯器做什麼,它幾乎總是很有啓發性。 – leander 2010-05-14 00:19:53

2

Vtable非常快。那麼簡單的條件。它們轉換爲CPU指令的單個數字。擔心這種性能會讓你陷入編譯器優化的晦澀境地,在那裏你完全不瞭解編譯器在做什麼。很有可能,程序中非常微妙的變化可能會超過if語句和vtable之間的微小差異。

我做了一點測試while ago測試RTTI的多個dispatch和vtable之間的差異。在釋放模式下,在兩百萬次迭代中完成的三個對象(兩個vtable調用)之間的分派需要62毫秒。這是不值得擔心的方式。

0

誰說#3使它不可能有一個通用的動畫容器?有幾種方法可以使用。他們全都歸結爲最終做出一個多態的電話,但選項在那裏。考慮到這一點:

std::vector<boost::any> generic_container; 
function(generic_container[0]); 

void function(boost::any & a) 
{ 
    my_operation::execute(a.type().name(), a); 
} 

my_operation只需要有一種註冊和按類型名稱篩選操作的方法。它搜索一個代表任何代表操作的函子,並使用它。函子然後any_casts到適當的時間,並執行類型特定的操作。

或使用訪客框架。以上就是這樣一種變化,但是在一個非常普通的層面上才能真正符合要求。

還有更多可能的方法。您可以存儲隱藏細節的類型,並在激活時執行正確的視圖選項,而不是存儲動畫。一個虛擬是被調用的,但它是專門用於切換彼此執行更復雜操作的具體類型。

換句話說,你的問題沒有一般的答案。根據你的需要,你可以達到各種複雜程度,使幾乎你的整個程序編譯時間多態而不是運行時。

3

(編輯爲簡潔起見。)

編譯器,CPU和操作系統都可以改變的答案,在這裏:

  • CPU:指令/數據緩存大小,結構和行爲,特別是任何智能預取
  • CPU:分支預測和推測執行行爲
  • CPU:罰用於錯誤預測轉移
  • 編譯器和CPU:可用性和rel有條件執行的指令ative成本(含分支機構的情況下幫助只包括一些指令)
  • 編譯器或鏈接:優化,可能會改變你的代碼,並刪除分支

總之,作爲Blindy在評論中說: :測試它。 =)

如果您正在爲現代桌面操作系統或操作系統編寫代碼,請獲得分析工具(valgrind,shark,codeanalyst,vtune等)的幫助 - 它可能會提供您從未知道的詳細信息尋找,如緩存未命中,分支預測失誤等。

即使您找不到一個好的答案,您也會從應用該工具中學到一些東西。我經常發現在反彙編時也很有啓發性(參見本主題中的一些其他答案)。

有些稍微推測音符:

  • 虛表傾向於導致的負載(這個+ 0),偏移量,第二負載,然後對寄存器的內容進行分支。你可以在其他答案中看到這一點。我熟悉的大多數CPU在預測寄存器分支時都很悲慘。
  • 布爾可能接近您正在使用的其他數據,因此可能已被緩存。分支目標也可能是固定的,因此對於預測和/或推測執行更加友好。
  • 在一些處理器上(這些日子很少見),加載bool而不是int會花費更多。我處理的ARM處理器上,我們偶爾會將vtables放在處理器核心的「緊密耦合的內存」中。大幅減少間接加載時間 - 就好像vtable始終處於緩存或更好狀態。

正如您所提到的,通常的規則適用:做什麼符合要求,首先是靈活/可維護/可讀,然後優化。

延伸閱讀/其他模式的追求:

無論是「面向數據的設計」還是「基於組件的實體」範例都有助於保持遊戲,多媒體引擎以及其他大於平均水平的其他事物提高性能,仍然希望保持代碼的有序性。 YMMV,當然。 =)

+0

從我提到數據導向設計+1。 – arke 2010-05-14 00:04:34