2016-11-17 68 views
1

背景:我開發了一個用C/C++編寫的計算密集型工具,必須能夠在各種不同的x86_64處理器上運行。爲了加快float和integer的計算速度,代碼中包含了很多SSE *內在函數,並針對不同的CPU SSE功能量身定製了不同的路徑。 (由於CPU標誌在程序開始時被檢測到並用於設置布爾值,因此我假設對於量身定製的代碼塊的分支預測將非常有效地工作)。在SSE內部函數中使用'錯誤'指令的gcc(6.1.0)

爲簡單起見,我認爲只有SSE2到SSE4.2需要考慮。

爲了訪問4.2路徑的SSE4.2 intrinsics,我需要使用gcc的-msse4.2選項。

問題 我遇到的問題是,至少在6.1.0,GCC去,並實現了SSE2內在的,mm_cvtsi32_si128,與SSE4.2指令,pinsrd。

如果我通過使用-msse2來限制編譯,它將使用sse2指令movd,即。英特爾「內在指南」說它應該使用的那個。

這是兩個煩人的問題。

1)關鍵的問題是,當程序在4.2版本的CPU上運行時,程序現在會崩潰並出現非法指令。我無法控制使用何種硬件,因此可執行文件需要與舊機器兼容,但需要利用新硬件上的功能。

2)根據Intel intrinsics指南,pinsrd指令比它取代的mov慢得多。 (pinsrd更通用但不需要)。

有誰知道如何使GCC 只是使用內部函數指南說應該使用卻仍然允許通過SSE4 *在同一編譯單元訪問所有SSE2指令?

更新:我還應該注意,使用各種不同的編譯器在Linux,Windows和OSX下編譯相同的代碼,所以如果可能的話,寧願避免或至少具有最少的編譯器特定擴展。

Update2 :(感謝@PeterCordes)似乎如果啓用了優化,gcc將在適當情況下恢復爲使用來自pinsrd的movd。

+1

嘗試看着嘩嘩的代碼生成一段時間 - 這將完全重新編寫SIMD內在的某些序列與它認爲是(而且經常是)一個「更好」的指令序列。 –

+2

你可以在gcc選擇PINSRD的地方顯示代碼嗎?你確定它沒有用它來優化掉下面的'_mm_unpacklo_epi32'什麼的? (PINSRD解碼爲與MOVD + PUNPCKLDQ相同的端口,但佔用較少的代碼字節,所以當您告訴編譯器允許使用SSE4.2指令時,這是個不錯的選擇)。 –

+1

無論如何,處理這個問題的常用方法是將您的SSE4函數放在與您的基準x86-64函數不同的獨立翻譯單元中。如果其中任何一個從其他翻譯單元調用小函數,鏈接時優化可能會有所幫助。 –

回答

5

如果在編譯步驟中將-msse4.2標誌賦予gcc的命令行,它將假定它可以自由使用整個轉換單元的SSE 4.2指令集。這可能導致你描述的行爲。如果您需要代碼只有使用SSE2和下面的代碼,然後使用-msse2(或者如果您正在爲x86_64構建,則根本沒有標誌)是必需的。

,我能想到的一些選項有:

  • 如果你可以很容易地打破你在函數級別的代碼,然後gcc的multiversioning功能可以幫助。它需要一個相對較新的編譯器版本,但它可以讓你做這樣的事情(從上面的鏈接所):

    __attribute__ ((target ("default"))) 
    int foo() 
    { 
        // The default version of foo. 
        return 0; 
    } 
    
    __attribute__ ((target ("sse4.2"))) 
    int foo() 
    { 
        // foo version for SSE4.2 
        return 1; 
    } 
    
    __attribute__ ((target ("arch=atom"))) 
    int foo() 
    { 
        // foo version for the Intel ATOM processor 
        return 2; 
    } 
    
    __attribute__ ((target ("arch=amdfam10"))) 
    int foo() 
    { 
        // foo version for the AMD Family 0x10 processors. 
        return 3; 
    } 
    
    int main() 
    { 
        int (*p)() = &foo; 
        assert ((*p)() == foo()); 
        return 0; 
    } 
    

    在這個例子中,GCC將自動編譯不同版本的foo()和派遣基於CPU的能力,在運行時是合適的。

  • 您可以將不同的實現(SSE2,SSE4.2等)分解爲不同的翻譯單元,然後在運行時適當地分派給正確的實現。

  • 您可以將所有SIMD代碼放入共享庫中,並使用不同的編譯器標誌多次構建共享庫。然後在運行時,您可以檢測CPU的功能並加載相應版本的共享庫。這是圖書館採用的方法,如Intel's Math Kernel Library

+0

謝謝,但沒有一個特別吸引人。 :-(相同的代碼也必須在Windows下使用MSVC和Mac編譯,因此代碼中編譯器特定的選項越少越好。 我確實覺得編譯器選擇使用較慢的指令( –

+0

@SimonF它可能是gcc中的一個bug,但你永遠不會知道;他們也可能有一個很好的理由,他們正在使用它。 SIMD代碼非常複雜,以至於在某個特定情況下,爲什麼一條指令可能比另一條指令更優先,往往存在非常微妙的因素,我不知道這個指令的具體細節 –

+1

@SimonF:如果您要比較'PINSRD xmm,r32,i'與'MOVD xmm,r32',你有它的倒退,它是延遲的3倍,吞吐量的2倍和一個額外的shuffle-port uop,並且錯誤地依賴於舊值因爲它的矢量寄存器合併而不是調整其餘的章程。當MOVD相當時,gcc極其不可能「偶然」使用它。這將是一個嚴重的編譯器錯誤。 –