舍入到2的幂(具有预处理器常量)

时间:2014-04-07 23:37:15

标签: c macros

使用unsigned int s可以像这样四舍五入为2的幂:

unsigned int power_of_2_max_u(unsigned int x)
{
    x -= 1;
    x = x | (x >> 1);
    x = x | (x >> 2);
    x = x | (x >> 4);
    x = x | (x >> 8);
    x = x | (x >>16);
    return x + 1;
}

...或

int is_power_of_2_i(int n)
{
    return (n & (n - 1)) == 0;
}

int power_of_2_max_i(int n)
{
    if (is_power_of_2_i(n))
        return n;

    do {
        n = n & (n - 1);
    } while (!is_power_of_2_i(n));

    return n * 2;
}

但是当值是常量时,应该可以使用预处理器来避免每次都计算值。即G:

i = power_of_2_max_u(sizeof(SomeStruct));

这是一个等同于power_of_2_max_u的宏。

#define POW2_CEIL(v) (1 + \
(((((((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) | \
     ((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) >> 0x04))) | \
   ((((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) | \
     ((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) >> 0x04))) >> 0x02))) | \
 ((((((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) | \
     ((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) >> 0x04))) | \
   ((((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) | \
     ((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) >> 0x04))) >> 0x02))) >> 0x01))))

由此Python-3脚本生成。

import math
BITS = 32
data = (["(v - 1)"] +
        ["(v | (v >> 0x%02x))" % i
         for i in reversed([2 ** j
         for j in range(int(math.log2(BITS)))])])
macro = "1 + v"
for l in reversed(data):
    macro = macro.replace('v', '(%s)' % l)
print("#define POW2_CEIL(v)", macro) 

使用statement expressions可以使它更好一点,但这依赖于GCC扩展。

#define POW2_CEIL(x_) ({      \
    unsigned int x = x_; \
    x -= 1;              \
    x = x | (x >> 1);    \
    x = x | (x >> 2);    \
    x = x | (x >> 4);    \
    x = x | (x >> 8);    \
    x = x | (x >>16);    \
    x + 1; })

虽然这是一个相当丑陋的宏,但我仔细检查过,编译器确实将其正确地减少为常量(因为它应该),因此unsigned int a = 8;完全等同于unsigned int a = POW2_CEIL(7);, 但我有兴趣知道是否有更好的方法(可能不限于int范围)。

2 个答案:

答案 0 :(得分:3)

  

但是当值是常量时,应该可以使用预处理器来避免每次都计算值。

可读性>>>性能。首先,问自己问题" "这是我代码中的真正瓶颈吗?"

如果答案是" no",那么就什么都不做;继续前进而不考虑微观 - "优化"你的代码过早。

即使这是代码的重要部分,也要考虑你的函数是纯粹的。它们的返回值仅取决于它们的参数,因为它们执行简单的计算(基本上是映射)。 很可能,任何体面的优化编译器都会在编译时使用已知的表达式调整函数的常量。

例如,以下代码......

#include <stdio.h>

static unsigned power_of_2_max_u(unsigned x)
{
    x -= 1;
    x = x | (x >> 1);
    x = x | (x >> 2);
    x = x | (x >> 4);
    x = x | (x >> 8);
    x = x | (x >>16);
    return x + 1;
}

struct Foo {
    int x;
    char c;
    int i;
};

int main()
{
    printf("%zu %u\n", sizeof(struct Foo), power_of_2_max_u(sizeof(struct Foo)));

    return 0;
}

...由clang -O2 -flto转换为以下程序集:

/tmp/constfold:
(__TEXT,__text) section
_main:
0000000100000f50    pushq   %rbp
0000000100000f51    movq    %rsp, %rbp
0000000100000f54    leaq    0x37(%rip), %rdi  ## literal pool for: "%zu %u\n"
0000000100000f5b    movl    $0xc, %esi        ## *** sizeof(struct Foo) == 12
0000000100000f60    movl    $0x10, %edx       ## *** rounded up == 16
0000000100000f65    xorl    %eax, %eax
0000000100000f67    callq   0x100000f70       ## symbol stub for: _printf
0000000100000f6c    xorl    %eax, %eax
0000000100000f6e    popq    %rbp
0000000100000f6f    retq

所以,你没有为编译器做任何以使你的函数不断折叠。

如果您可以观察到不断折叠,只考虑将您的函数重写为上面的一个宏(或使用非可移植编译器内在函数)。

答案 1 :(得分:0)

如何使用内置的漂亮编译器(在gcc和clang中工作):

int msb = 31 - __builtin_clz(v);
int log2_value = v > (1U << msb) ? msb + 1 : msb;

或者,对于64b案例:

int msb = 63 - __builtin_clzl(v);
int log2_value = v > (1ULL << msb) ? msb + 1 : msb;

(v是输入变量)。

将上述内容包装成宏或c ++ constexpr留给读者练习。

使用MSVC,类似于__builtin_clz的内置版本称为_BitScanReversehttp://msdn.microsoft.com/en-us/library/fbxyd7zd%28v=vs.85%29.aspx)。