2011-04-04 26 views
6

我目前正在嘗試爲我的圖書館創建高度優化的可重用函數。舉例來說,我寫的函數「是2的冪」的方式如下:對於x64而不是內聯彙編可以使用「自定義內部函數」嗎?

template<class IntType> 
inline bool is_power_of_two(const IntType x) 
{ 
    return (x != 0) && ((x & (x - 1)) == 0); 
} 

這是一種便攜式,低維護的實現爲內嵌C++模板。此代碼是由VC++ 2008編譯下面的代碼分支:

is_power_of_two PROC 
    test rcx, rcx 
    je SHORT [email protected]_power_o 
    lea rax, QWORD PTR [rcx-1] 
    test rax, rcx 
    jne SHORT [email protected]_power_o 
    mov al, 1 
    ret 0 
[email protected]_power_o: 
    xor al, al 
    ret 0 
is_power_of_two ENDP 

我還發現,從這裏實施:"The bit twiddler",這將彙編編碼爲64位,如下所示:

is_power_of_two_fast PROC 
    test rcx, rcx 
    je SHORT NotAPowerOfTwo 
    lea rax, [rcx-1] 
    and rax, rcx 
    neg rax 
    sbb rax, rax 
    inc rax 
    ret 
NotAPowerOfTwo: 
    xor rax, rax 
    ret 
is_power_of_two_fast ENDP 

我在彙編模塊(.asm文件)中測試了兩個與C++分開編寫的子例程,而第二個子程序的工作速度大約快20%!然而,函數調用的開銷是相當大的:如果我將第二個程序集實現「is_power_of_two_fast」與模板函數的內聯版本進行比較,後者儘管分支更快!

不幸的是,x64的新約定指定不允許內聯彙編。人們應該使用「內在功能」。

現在的問題是:我可以實現更快的版本「is_power_of_two_fast」作爲自定義內部函數或類似的東西,以便它可以內聯使用嗎?或者,是否有可能以某種方式強制編譯器生成函數的低分支版本?

+0

GCC和ICC仍允許內聯組件 – hirschhornsalz 2011-04-04 11:50:32

+0

通過使用&代替&&避免分支。 – 2011-04-04 18:29:46

+0

@drhirsch:謝謝,我牢記在心。 @Hans Passant:我已經嘗試過了,但是會導致代碼變慢(太多指令)。 – 2011-04-05 07:10:25

回答

2

即使VC2005也能夠生成代碼用sbb指令。

爲C代碼

bool __declspec(noinline) IsPowOf2(unsigned int a) 
{ 
    return (a>=1)&((a&(a-1))<1); 
} 

編譯以下

00401000 lea   eax,[ecx-1] 
00401003 and   eax,ecx 
00401005 cmp   eax,1 
00401008 sbb   eax,eax 
0040100A neg   eax 
0040100C cmp   ecx,1 
0040100F sbb   ecx,ecx 
00401011 add   ecx,1 
00401014 and   eax,ecx 
00401016 ret   
0

唯一的出路就是退後一步,開始看更大的圖片。要麼停止實施微型優化的API,要麼進行更大的API調用,在MASM64,YASM,NASM等中進行優化。

如果使用更強大的彙編器之一,可以將小函數變成宏,基於內聯彙編程序的C/C++頭文件轉換爲彙編程序包含文件。

2

不,您不能實現任何自定義內部函數,它們都內置於編譯器中。它不僅是內置的指令,而且編譯器也知道內在的語義,並根據不同的周圍代碼調整代碼。對於內聯組件

的一個原因爲x86-64的被除去的是,插入組件插入函數的中間擾亂優化,並且常常導致在周圍的彙編代碼較差優化代碼。那裏很容易出現淨虧損!

唯一真正使用了內聯函數是編譯器不能從C或C++結構產生,如BSF和BSR「有趣」的特別說明。使用內聯函數可以更好地工作,比如上面的模板。

如果你需要做一些特別的東西,編譯器不明白,唯一真正的選擇是編寫全功能作爲一個單獨的彙編模塊。如果該函數的調用開銷太昂貴,那麼優化可能首先不值得。

相信你的編譯器(tm)!

1

在這種簡單的情況下,VC10 x64內在函數不會有很大的幫助。 您擁有的動態分支是由於運營商是早期運營商的& &。 在許多情況下(你的情況是一個完美的例子),最好避免通過計算所有分支的結果來進行分支,然後應用掩碼來選擇好的分支。與蒙一段cpp的代碼是這樣:

template<typename T_Type> 
inline bool isPowerOfTwo(T_Type const& x) 
{ 
    // static type checking for the example 
    static_assert(std::is_integral<T_Type>::value && std::is_unsigned<T_Type>::value, "limited to unsigned types for the example"); 
    typedef std::make_signed<T_Type>::type s_Type; 

    // same as yours but with no branching 
    return bool( ((s_Type(s_Type(x != 0) << (s_Type(sizeof(T_Type)<<3u)-1))) >> (s_Type(s_Type(sizeof(T_Type)<<3u)-1))) & ((x & (x - 1)) == 0) ); 
} 

在上面的代碼,我沒有檢查,如果數字是負數,或者不爲簽署的類型。通過對右(numBit-1)次執行算術移位以得到負值的(〜0)值以及0爲正值的值,再次簡單的掩碼將執行該技巧

+0

不幸的是,與最初的C++函數不同的是,它並沒有太大的不同。彙編輸出的編譯顯示,VC++ 2008在編譯代碼時使用「test」指令,並且分支仍然存在。 – 2011-11-10 20:20:58

相關問題