2013-01-18 29 views
7

我發展我的3D計算的優化,我現在有:在同一個可執行不同的優化(平原,SSE,AVX)用C/C++

  • 使用標準的C一「plain」版本語言庫,
  • 一個SSE優化版本,編譯使用預處理器#define USE_SSE
  • 一個AVX優化版本,編譯使用預處理器#define USE_AVX

是否可以在3個版本之間切換而不必編譯不同的可執行文件(例如,有不同的庫文件並動態加載「正確」的函數,不知道函數是否正確)? 我會考慮在軟件中使用這種開關的表現。

+1

沒有提及該平臺?即使您知道這些指令永遠不會被調用,某些平臺也會拒絕使用avx運行代碼。一些平臺有ifunc可以在運行時在多個實現之間進行選擇。有些平臺在依賴於功能的路徑中查找共享庫。 –

回答

5

一種方法是實現符合相同接口的三個庫。藉助動態庫,您可以交換庫文件,並且可執行文件將使用它找到的任何內容。例如在Windows上,你可以編譯3個DLL文件:

  • PlainImpl.dll
  • SSEImpl.dll
  • AVXImpl.dll

然後make可執行鏈接對Impl.dll。現在只需將三個特定DLL中的一個放入與.exe相同的目錄中,將其重命名爲Impl.dll,它將使用該版本。同樣的原則應該基本上適用於類UNIX操作系統。

下一個步驟將是編程方式加載庫,這可能是最靈活的,但它是操作系統特定的,需要一些更多的工作(如打開庫,獲取函數指針等)

編輯:但是,當然,你可以實現三次函數,並在運行時選擇一個函數,具體取決於某些參數/配置文件設置等,如其他答案中所示。

0

當然這是可能的。

要做到這一點的最好方法是具有執行完整作業的功能,並在運行時在其中進行選擇。這會工作,但不是最優的:

typedef enum 
{ 
    calc_type_invalid = 0, 
    calc_type_plain, 
    calc_type_sse, 
    calc_type_avx, 
    calc_type_max // not a valid value 
} calc_type; 

void do_my_calculation(float const *input, float *output, size_t len, calc_type ct) 
{ 
    float f; 
    size_t i; 

    for (i = 0; i < len; ++i) 
    { 
     switch (ct) 
     { 
      case calc_type_plain: 
       // plain calculation here 
       break; 
      case calc_type_sse: 
       // SSE calculation here 
       break; 
      case calc_type_avx: 
       // AVX calculation here 
       break; 
      default: 
       fprintf(stderr, "internal error, unexpected calc_type %d", ct); 
       exit(1); 
       break 
     } 
    } 
} 

每次通過循環,代碼執行switch聲明,這只是開銷。一個非常聰明的編譯器可以從理論上爲你修復它,但更好的是自己修復它。

而是編寫三個獨立的函數,一個用於普通,一個用於SSE,另一個用於AVX。然後在運行時決定運行哪一個。

對於獎勵積分,在「調試」版本中,使用SSE和平原進行計算,並聲明結果足夠接近以提供信心。寫簡單的版本,不是爲了速度,而是爲了正確;然後使用其結果來驗證您的智能優化版本是否可以得到正確的答案。

傳說中的約翰卡馬克推薦後一種方法;他稱之爲「並行實施」。請閱讀his essay

所以我建議你先寫簡單的版本。然後,返回並開始使用SSE或AVX加速重寫應用程序的某些部分,並確保加速版本提供正確的答案。 (有時,普通版本可能有錯誤,加速版本沒有。有兩個版本,並比較它們有助於使蟲子來在任一版本的光。)

+2

如果你正在考慮優化,我懷疑你想在循環中做這樣的檢查... –

+0

是的,你寧願把循環放在爲每個'switch'分支調用的函數中。 –

+1

甚至更​​好,有一個接口類,它擴展並使用3個優化實現...多態開關。 –

6

這有幾種解決方案。

一種是基於C++,在那裏你會創建多個類 - 通常情況下,你實現一個接口類,並使用工廠函數給你一個正確的類的對象。

例如

class Matrix 
{ 
    virtual void Multiply(Matrix &result, Matrix& a, Matrix &b) = 0; 
    ... 
}; 

class MatrixPlain : public Matrix 
{ 
    void Multiply(Matrix &result, Matrix& a, Matrix &b); 

}; 


void MatrixPlain::Multiply(...) 
{ 
    ... implementation goes here... 
} 

class MatrixSSE: public Matrix 
{ 
    void Multiply(Matrix &result, Matrix& a, Matrix &b); 
} 

void MatrixSSE::Multiply(...) 
{ 
    ... implementation goes here... 
} 

... same thing for AVX... 

Matrix* factory() 
{ 
    switch(type_of_math) 
    { 
     case PlainMath: 
      return new MatrixPlain; 

     case SSEMath: 
      return new MatrixSSE; 

     case AVXMath: 
      return new MatrixAVX; 

     default: 
      cerr << "Error, unknown type of math..." << endl; 
      return NULL; 
    } 
} 

或者,如上所述,您可以使用具有通用接口的共享庫,並動態加載合適的庫。當然,如果你將Matrix基類作爲你的「普通」類來實現,你可以逐步細化,只實現你實際發現的部分是有益的,並且依賴於基類來實現性能不是「高度crticial。

編輯: 你說說行內,我認爲你是在尋找的功能錯誤的水平,如果是這樣的話。你需要相當大的函數來處理相當多的數據。否則,您將花費所有的精力將數​​據準備成正確的格式,然後執行一些計算指令,然後將數據存回內存。

我也會考慮你如何存儲你的數據。你是用X,Y,Z,W存儲一個數組的集合嗎?還是你在單獨的數組中存儲大量的X,很多的Y,很多的Z和很多的W [假設我們正在進行3D計算]?根據您的計算工作方式,您可能會發現採用其中一種方式會給您帶來最好的收益。

我已經完成了一點SSE和3DNow!幾年前的優化,而「技巧」通常更多地涉及如何存儲數據,以便您可以輕鬆地一次性獲取正確類型數據的「捆綁」。如果數據以錯誤的方式存儲,那麼您將浪費大量時間「調整數據」(將數據從一種存儲方式移動到另一種方式)。

+0

+1用於逐步細化 –

+0

此方法的問題是無法編譯和優化不同體系結構的不同功能。如果使用'-march = i7'編譯所有內容,即使C版本只能在i7上運行,如果使用'-march = i686'編譯,它將運行在過去15年內構建的每臺機器上,但某些內部函數如SSE/AVX)將不可用,優化器將僅使用SSE/AVX版本中的可用指令的子集。 – hirschhornsalz

+0

因此,在獨立的源文件中構建代碼。儘管我發現如果您真的想要以一種非常好的方式使用SSE/AVX指令,您將需要使用內聯彙編程序。編譯器通常不會在「很聰明」方面做得很好。 –