8

我目前正在尝试为我的库创建高度优化的、可重用的函数。例如,我用以下方式编写函数“是 2 的幂”:

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 $LN3@is_power_o
    lea rax, QWORD PTR [rcx-1]
    test    rax, rcx
    jne SHORT $LN3@is_power_o
    mov al, 1
    ret 0
$LN3@is_power_o:
    xor al, al
    ret 0
is_power_of_two ENDP

我还从这里找到了实现:"The bit twiddler",它将在 x64 的汇编中编码如下:

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”实现为自定义内在函数或类似的东西,以便可以内联使用?或者,是否有可能以某种方式强制编译器生成函数的低分支版本?

4

4 回答 4

3

不,您不能实现任何自定义内在函数,它们都内置在编译器中。不仅是内置的指令,编译器也知道内在的语义,并为不同的周围代码调整代码。

为 x86-64 删除内联汇编的一个原因是,将汇编插入函数中间会干扰优化器,并且通常会导致围绕汇编代码的代码优化得不太好。那里很容易出现净亏损!

内在函数的唯一真正用途是编译器无法从 C 或 C++ 构造(如 BSF 或 BSR)生成的“有趣”特殊指令。使用内联函数(如上面的模板),大多数其他事情都会更好地工作。

如果您需要做一些编译器不理解的特殊操作,唯一真正的选择是将整个函数编写为单独的汇编程序模块。如果该函数的调用开销太高,那么优化可能一开始就不值那么多钱。

相信你的编译器(tm)!

于 2011-04-04T17:58:05.680 回答
2

甚至 VC 2005 也能够使用 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          
于 2012-04-16T23:29:28.177 回答
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值。

于 2011-10-19T17:24:57.040 回答
0

前进的唯一方法是退后一步,开始放眼大局。要么停止实现微优化的 API,要么继续进行更大的 API 调用,所有这些都在 MASM64、YASM、NASM 等中进行了优化。

如果您使用更强大的汇编器之一,您可以将小函数转换为宏,因此基本上将基于 C/C++ 标头的内联汇编器函数更改为汇编器包含文件。

于 2011-04-04T09:09:00.280 回答