使用按位运算符的效率

时间:2012-07-27 08:43:55

标签: c bit-manipulation

要求如下:

/* length must be >= 18 */

int calcActualLength(int length) {
    int remainder = (length - 18) % 8;
    if (remainder == 0)
        return length;
    return length + 8 - remainder;
}

使用逐位运算符,我可以重构第1行

int remainder = (length - 2) & 7;

可以进一步优化吗?

2 个答案:

答案 0 :(得分:2)

((length+5)&~7)+2

int calcActualLength(int length) {
    int remainder = (length - 18) % 8;
    if (remainder == 0)
        return length;
    return length + 8 - remainder;
}
==>
int HELPER_calcActualLength(int length) {
    int remainder = length % 8;
    if (remainder == 0)
        return length;
    return length + 8 - remainder;
}
int calcActualLength(int length) {
    return 18 + HELPER_calcActualLength(length - 18);
}

当参数> = 0时,HELPER_calcActualLength()等于ROUNDUP_8()语义

更简单的ROUNDUP_8()可以是:

#define ROUNDUP_8(x) (((x)+7)&~7)

int calcActualLength(int length) {
    return 18 + ROUNDUP_8(length - 18);
}
==>    2 + ROUNDUP_8(length - 18 + 16);
==>    2 + ROUNDUP_8(length - 2);
==>    2 + (((length - 2)+7)&~7)
==>    ((length+5)&~7)+2

答案 1 :(得分:2)

使用gcc -O3进行编译时,原始代码会生成以下64位程序集:

        movl    %edi, %eax
        leal    -18(%rax), %ecx
        movl    %ecx, %edx
        sarl    $31, %edx
        shrl    $29, %edx
        addl    %edx, %ecx
        andl    $7, %ecx
        subl    %edx, %ecx
        je      .L2
        addl    $8, %eax
        subl    %ecx, %eax
.L2:
        rep

正如您对问题的评论中所建议的那样,将参数更改为unsigned int可以更好地进行优化并导致以下汇编:

        leal    -18(%rdi), %edx
        movl    %edi, %eax
        andl    $7, %edx
        je      .L3
        leal    8(%rdi), %eax
        subl    %edx, %eax
.L3:
        rep

可以通过添加8并使用7屏蔽来累计到~7的倍数。它的工作方式如下:如果最后三位不是全为零,则将7加载到第4位,否则不会发生进位。所以你的功能可以简化为:

return (((length - 18) + 7) & ~7) + 18;

或更简单:

return ((length - 11) & ~7) + 18;

GCC简单编译最后一行:

        leal    -11(%rdi), %eax
        andl    $-8, %eax
        addl    $18, %eax

请注意,lea(加载有效地址)说明因其计算reg1 + size*reg2 + offset等简单线性组合的能力而经常被“滥用”