2012-07-23 53 views
13

我有一個類是這樣的:結構 - 性能差

//Array of Structures 
class Unit 
{ 
    public: 
    float v; 
    float u; 
    //And similarly many other variables of float type, upto 10-12 of them. 
    void update() 
    { 
     v+=u; 
     v=v*i*t; 
     //And many other equations 
    } 
}; 

創建單元類型的對象的數組。並致電更新。

int NUM_UNITS = 10000; 
void ProcessUpdate() 
{ 
    Unit *units = new Unit[NUM_UNITS]; 
    for(int i = 0; i < NUM_UNITS; i++) 
    { 
    units[i].update(); 
    } 
} 

爲了加快速度,可能會自動化循環,我將AoS轉換爲數組結構。

//Structure of Arrays: 
class Unit 
{ 
    public: 
    Unit(int NUM_UNITS) 
    { 
    v = new float[NUM_UNITS]; 
    } 
    float *v; 
    float *u; 
    //Mnay other variables 
    void update() 
    { 
    for(int i = 0; i < NUM_UNITS; i++) 
    { 
     v[i]+=u[i]; 
     //Many other equations 
    } 
    } 
}; 

當循環失敗autovectorize,我得到一個非常糟糕的性能的數組結構。對於50個單位,SoA的更新比AoS稍快。但從100個單位開始,SoA比AoS慢。在300臺機組中,SoA差不多是它的兩倍。在100K單位,SoA比AoS慢4倍。雖然緩存可能是SoA的一個問題,但我並沒有預料到性能差異會如此之高。對cachegrind進行性能分析,顯示兩種方法的缺失數量相似。 Unit對象的大小是48個字節。 L1緩存爲256K,L2爲1MB,L3爲8MB。我在這裏錯過了什麼?這真的是緩存問題嗎?

編輯: 我使用的是gcc 4.5.2。編譯器選項是-o3 -msse4 -ftree-vectorize。

我在SoA做了另一個實驗。我沒有動態分配數組,而是在編譯時分配了「v」和「u」。當有100K個單元時,這個性能比具有動態分配陣列的SoA快10倍。這裏發生了什麼事?爲什麼靜態和動態分配內存之間存在性能差異?

+0

你用什麼編譯器選項來構建它? – 2012-07-23 16:57:23

+0

不知道這是否會有所作爲,但[std :: valarray](http://gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/a00738.html)可能會(或可能不會) ) 幫幫我。它被設計用於在整個數組上執行數學運算(比較乾淨的語法),但是我猜測實現者有特殊的重載來嘗試優化這些操作並在可能時進行智能分配等。它可能根本沒有幫助,但可能值得一看。 – pstrjds 2012-07-23 17:16:54

+1

在運行基準測試之前將數據集清零會發生什麼?未初始化的浮點很有可能被[非規範化](http://stackoverflow.com/a/9314926/922184)。你不想那樣搞砸你的基準。 – Mysticial 2012-07-25 03:27:28

回答

9

在這種情況下,數組的結構不是緩存友好的。

您同時使用uv,但是如果兩個不同的陣列對應它們,它們將不會同時加載到一個緩存行中,並且緩存未命中會造成巨大的性能損失。

_mm_prefetch可用於使AoS表示更快。

+0

是否有'_mm_prefetch'的GCC/clang等價物? – 2012-07-23 17:02:40

+0

http://gcc.gnu.org/ml/gcc-patches/2008-01/msg01425.html – 2012-07-23 17:08:30

+2

然而,緩存未命中並沒有被部分浪費 - 你得到的東西是你將*需要的東西(更多的「u」 's和更多'v''s),那爲什麼會降低性能? – harold 2012-07-23 17:23:07

1

預取對於花費其大部分執行時間等待數據顯示的代碼至關重要。現代前端總線有足夠的帶寬,預取應該是安全的,只要你的程序沒有超出當前負載的範圍。

由於各種原因,結構和類可以在C++中創建大量性能問題,並且可能需要更多調整才能獲得可接受的性能水平。當代碼很大時,使用面向對象的編程。當數據很大(並且性能很重要)時,不要。

float v[N]; 
float u[N]; 
    //And similarly many other variables of float type, up to 10-12 of them. 
//Either using an inlined function or just adding this text in main() 
     v[j] += u[j]; 
     v[j] = v[j] * i[j] * t[j]; 
+1

我不認爲OOP不應該與使用AoS混淆。標量場可以被認爲是OOP中的一個對象,就像它在數學中被認爲是一個對象一樣,但是如果使用多個標量場表示一個空間區域,那麼您將以與OOP一致的方式使用SoA。這歸結於你認爲OOP中的對象。 – 16807 2017-03-02 18:18:08

0

當然,如果你沒有實現向量化,那麼沒有太多的動機來進行SoA轉換。

除了__RESTRICT事實上接受的相當寬泛,gcc 4.9已採用#pragma GCC ivdep來打破假定的別名依賴關係。對於顯式預取的使用,如果它是有用的,當然你可能需要更多的SoA。主要的觀點可能是通過提前提取頁面來加速DTLB未命中的解決方案,因此您的算法可能會變得更加緩慢。

我不認爲可以對任何你稱爲「編譯時」分配的智能註釋做出更多細節,包括關於你的操作系統的細節。毫無疑問,高水平分配和重新使用分配的傳統非常重要。