2011-04-04 31 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

相关问题