我發展我的3D計算的優化,我現在有:在同一個可執行不同的優化(平原,SSE,AVX)用C/C++
- 使用標準的C一「
plain
」版本語言庫, - 一個
SSE
優化版本,編譯使用預處理器#define USE_SSE
, - 一個
AVX
優化版本,編譯使用預處理器#define USE_AVX
是否可以在3個版本之間切換而不必編譯不同的可執行文件(例如,有不同的庫文件並動態加載「正確」的函數,不知道函數是否正確)? 我會考慮在軟件中使用這種開關的表現。
我發展我的3D計算的優化,我現在有:在同一個可執行不同的優化(平原,SSE,AVX)用C/C++
plain
」版本語言庫,SSE
優化版本,編譯使用預處理器#define USE_SSE
,AVX
優化版本,編譯使用預處理器#define USE_AVX
是否可以在3個版本之間切換而不必編譯不同的可執行文件(例如,有不同的庫文件並動態加載「正確」的函數,不知道函數是否正確)? 我會考慮在軟件中使用這種開關的表現。
一種方法是實現符合相同接口的三個庫。藉助動態庫,您可以交換庫文件,並且可執行文件將使用它找到的任何內容。例如在Windows上,你可以編譯3個DLL文件:
然後make可執行鏈接對Impl.dll
。現在只需將三個特定DLL中的一個放入與.exe
相同的目錄中,將其重命名爲Impl.dll
,它將使用該版本。同樣的原則應該基本上適用於類UNIX操作系統。
下一個步驟將是編程方式加載庫,這可能是最靈活的,但它是操作系統特定的,需要一些更多的工作(如打開庫,獲取函數指針等)
編輯:但是,當然,你可以實現三次函數,並在運行時選擇一個函數,具體取決於某些參數/配置文件設置等,如其他答案中所示。
當然這是可能的。
要做到這一點的最好方法是具有執行完整作業的功能,並在運行時在其中進行選擇。這會工作,但不是最優的:
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加速重寫應用程序的某些部分,並確保加速版本提供正確的答案。 (有時,普通版本可能有錯誤,加速版本沒有。有兩個版本,並比較它們有助於使蟲子來在任一版本的光。)
如果你正在考慮優化,我懷疑你想在循環中做這樣的檢查... –
是的,你寧願把循環放在爲每個'switch'分支調用的函數中。 –
甚至更好,有一個接口類,它擴展並使用3個優化實現...多態開關。 –
這有幾種解決方案。
一種是基於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!幾年前的優化,而「技巧」通常更多地涉及如何存儲數據,以便您可以輕鬆地一次性獲取正確類型數據的「捆綁」。如果數據以錯誤的方式存儲,那麼您將浪費大量時間「調整數據」(將數據從一種存儲方式移動到另一種方式)。
+1用於逐步細化 –
此方法的問題是無法編譯和優化不同體系結構的不同功能。如果使用'-march = i7'編譯所有內容,即使C版本只能在i7上運行,如果使用'-march = i686'編譯,它將運行在過去15年內構建的每臺機器上,但某些內部函數如SSE/AVX)將不可用,優化器將僅使用SSE/AVX版本中的可用指令的子集。 – hirschhornsalz
因此,在獨立的源文件中構建代碼。儘管我發現如果您真的想要以一種非常好的方式使用SSE/AVX指令,您將需要使用內聯彙編程序。編譯器通常不會在「很聰明」方面做得很好。 –
沒有提及該平臺?即使您知道這些指令永遠不會被調用,某些平臺也會拒絕使用avx運行代碼。一些平臺有ifunc可以在運行時在多個實現之間進行選擇。有些平臺在依賴於功能的路徑中查找共享庫。 –