获得2的功率小于一定数量的最快方法是什么?

时间:2014-08-09 15:36:56

标签: c 64-bit

我正在使用这个逻辑:

while ((chase<<(++n))< num) ;

其中chase=1n=0最初和num是我想要找到2的幂的值,该值仅小于它。

循环后我只需应用

chase=1; 
chase<<(n-1);

虽然我得到了正确的答案,但我只是想找到最快的方法。有没有更快的方法?

4 个答案:

答案 0 :(得分:2)

对于正整数v,2 小于或等于v的幂为2^[log2(v)](即1 << [log2(v)]),其中{{1} } []代表舍入 down 。 (如果您需要2的幂, <{1}},则可以轻松调整算法。)

对于非零[log2(v)]vv的二进制表示中最高1位的索引。

您必须已经知道以上所有内容,因为这正是您在原始代码中所做的。但是,它可以更有效地完成。 (当然,分析并查看新的&#34;更高效的解决方案是否实际上对您的数据更有效率总是一个好主意。)

用于查找最高1位索引的通用平台无关技巧基于DeBruijn序列。这是32位整数的实现

[log2(v)]

还有其他位算法解决方案,可在此处找到:https://graphics.stanford.edu/~seander/bithacks.html

但是,如果您需要最高效率,请注意许多现代硬件平台本地支持此操作,并且编译器提供访问相应硬件功能的内在函数。例如,在MSVC中,它可能如下所示

v

在GCC中,它可能如下所示

unsigned ulog2(uint32_t v)
{ /* Evaluates [log2 v] */
  static const unsigned MUL_DE_BRUIJN_BIT[] = 
  {
     0,  9,  1, 10, 13, 21,  2, 29, 11, 14, 16, 18, 22, 25,  3, 30,
     8, 12, 20, 28, 15, 17, 24,  7, 19, 27, 23,  6, 26,  5,  4, 31
  };

  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;

  return MUL_DE_BRUIJN_BIT[(v * 0x07C4ACDDu) >> 27];
}

如果您需要64位版本的功能,可以用相同的方式重写它们。或者,由于对数的良好属性,您可以通过将64位值分成两个32位半并将32位函数应用于最高位非零半来轻松构建它们。

答案 1 :(得分:0)

获得2的最大功率小于一定数量的最快方法是使用查找数组。

int lk[] = {-9999999, 0, 1, 2, 2, 4, 4, 4, 4, 8, ...};
printf("the greatest power of two less than %d is %d.\n", num, lk[num]);

答案 2 :(得分:0)

答案取决于机器可以多快地进行内存访问,与位移和条件跳转相比。假设您使用的是16位数字。然后,您可以执行基于时间的算法的16个步骤,或者首先分配65K数组,然后执行查找。如果您需要进行多次比较并且数字或多或少是随机的(不是非常小或大),我会说查找最好的交易。但是对于32位数字,你需要一个4GB阵列,这听起来不太合理。对于64位数字,这是不可行的。

答案 3 :(得分:0)

对于单个答案,问题太模糊了。 你无法确定&#34;速度&#34;仅从源代码开始。

OP的问题类似于&#34;什么是最快的砖?&#34;

某些C代码运行的速度取决于所使用的编译器,使用的体系结构(操作系统和硬件),有时甚至是使用的运行时环境。 (某些环境变量控制C库函数的工作方式,在许多操作系统上。如果用户需要,几乎所有C库函数都可以通过自定义函数插入而无需在Linux中重新编译。)

这甚至不是C特有的,但对所有编程语言都是通用的。即使处理器执行的本机二进制代码的速度也会因精确的处理器类型而异。 x86处理器系列就是一个很好的例子。

你经常可以判断一块砖是否很慢,因为某些特征往往总是很慢,或者因为一块砖明显较差(算法上或其他方面) - 比如,执行其功能所需要的更复杂。

只有在您足够精确地定义环境时,您才能知道哪块砖是最快的砖。

然而,很容易忘记,从乐高积木到建筑材料到无功能的电子设备,还有许多不同类型的积木。换句话说,有几个与C:K&amp; R C,C89,C99,C11,POSIX C标准相关的标准,甚至是常用的编译器内置函数和向量扩展。


注意:OP指定&#34; 2的幂小于[argument]&#34; ,这意味着对于无符号整数,

argument  result
    0        0     (Invalid argument; all powers of two are larger)
    1        0     (Any negative number; but we use unsigned integers)
    2        1
    3        2
    4        2
    5        4
    6        4
    7        4
    8        4
    9        8

等等。

许多CPU架构都有一个汇编指令来查找数字中设置的最高位,就像AndreyT在另一个问题的答案中所描述的那样。要找到小于指定参数的2的幂,检查单独设置的最高位是否等于参数,如果是,则返回下一个较小的2的幂:

#include <stdint.h>

#if defined(__GNUC__)
static int highest_bit_set(uint32_t value)
{
    if (sizeof (unsigned int) == sizeof value)
        return 31 - __builtin_clz(value);
    else
    if (sizeof (unsigned long) == sizeof value)
        return 31 - __builtin_clzl(value);
    else
        exit(127); /* Weird architecture! */
}
#elif defined (__MSC_VER)
static int highest_bit_set(unsigned long value)
{
    unsigned long result;
    if (_BitScanReverse(&result, value))
        return result;
    else
        return -1;
}
#endif

uint32_t smaller_power_of_two(uint32_t value)
{
    uint32_t result;

    if (!value)
        return 0;

    result = ((uint32_t)1) << highest_bit_set(value);

    if (result == value)
        return result >> 1;

    return result;
}

GCC在编译时优化sizeof条件,但不会产生接近x86的最佳汇编。对于x86,使用bsr指令的AT&amp; T汇编函数可以简化为

smaller_power_of_two:
    movl    4(%esp), %edx
    movl    $1, %eax
    cmpl    %eax, %edx
    jbe     .retzero
    bsrl    %ecx, %edx
    sall    %cl, %eax
    movl    %eax, %ecx
    shrl    %ecx
    cmpl    %eax, %edx
    cmovbe  %ecx, %eax
    ret

.retzero:
    xorl    %eax, %eax
    ret

有两个比较,一个分支和一个条件移动。在Cortex-M4微控制器上,GCC-4.8产生以下漂亮的汇编代码(为简洁起见省略了前导码和abi标志):

smaller_power_of_two:
    cbz     r0, .end1
    clz     r3, r0
    movs    r2, #1
    rsb     r3, r3, #31
    lsl     r3, r2, r3
    cmp     r3, r0
    it      ne
    movne   r0, r3
    beq     .end2
.end1:
    bx      lr
.end2:
    lsr     r0, r3, r2
    bx      lr

随机存储器访问往往具有不可忽视的成本,因为缓存是有限的,并且与典型CPU的算术逻辑功能相比,RAM访问速度较慢。根据我的经验,x86架构上的查找表往往导致代码比使用一些简单的算术运算(加法,减法,按位AND / OR / NOT / XOR)计算值更慢。高速缓存未命中经常出现在其他地方(因为查找表占用了宝贵的高速缓存,而其他用于缓存一些其他数据),所以只考虑使用查找表的函数不会产生可靠的实际估计值。世界表现,只有一个不切实际的&#34;如果世界是完美的&#34; 型最佳估计。

假设一个通用的C编译器,没有编译器内置函数或内联汇编,那么清除最低有效位集的旧value & (value - 1)技巧通常非常快:

uint32_t smaller_power_of_two(uint32_t value)
{
    uint32_t result;

    if (!value)
        return (uint32_t)0;

    do {
        result = value;
        value &= value - (uint32_t)1;
    } while (value);

    return result;
}

以上评估值至少一次,最多为其中设置的位数,在每次迭代期间使用赋值,AND和减法(减量)。我可能在我自己的代码中使用上述版本,除非我有关于所使用的体系结构和编译器的更多信息。

在某些体系结构上,条件分支非常慢,因此基于下一个更大功率的变体可能会更快:

#include <stdint.h>

uint32_t smaller_power_of_two(uint32_t value)
{
    if (value > (uint32_t)2147483648UL)
        return (uint32_t)2147483648UL;
    else
    if (value < (uint32_t)2)
        return (uint32_t)0;

    value--;
    value |= value >> 1;
    value |= value >> 2;
    value |= value >> 4;
    value |= value >> 8;
    value |= value >> 16;

    return (value + (uint32_t)1) >> 1;
}

如果没有if子句,这只会产生1到2147483648之间参数的正确结果。尽管如此,这段代码管道不畅,所以在超标量CPU上它不会像它那样利用CPU。

如果条件分支(基于整数比较)很快,则可以使用二进制搜索。对于32位无符号整数,最多需要针对任何可能的输入值评估六个if条件,以获得正确的结果:

uint32_t smaller_power_of_two(uint32_t value)
{
    if (value > (uint32_t)65536UL) {
        if (value > (uint32_t)16777216UL) {
            if (value > (uint32_t)268435456UL) {
                if (value > (uint32_t)1073741824UL) {
                    if (value > (uint32_t)2147483648UL) {
                        return (uint32_t)2147483648UL;
                    } else {
                        return (uint32_t)1073741824UL;
                    }
                } else {
                    if (value > (uint32_t)536870912UL) {
                        return (uint32_t)536870912UL;
                    } else {
                        return (uint32_t)268435456UL;
                    }
                }
            } else {
                if (value > (uint32_t)67108864UL) {
                    if (value > (uint32_t)134217728UL) {
                        return (uint32_t)134217728UL;
                    } else {
                        return (uint32_t)67108864UL;
                    }
                } else {
                    if (value > (uint32_t)33554432UL) {
                        return (uint32_t)33554432UL;
                    } else {
                        return (uint32_t)16777216UL;
                    }
                }
            }
        } else {
            if (value > (uint32_t)1048576UL) {
                if (value > (uint32_t)4194304UL) {
                    if (value > (uint32_t)8388608UL) {
                        return (uint32_t)8388608UL;
                    } else {
                        return (uint32_t)4194304UL;
                    }
                } else {
                    if (value > (uint32_t)2097152UL) {
                        return (uint32_t)2097152UL;
                    } else {
                        return (uint32_t)1048576UL;
                    }
                }
            } else {
                if (value > (uint32_t)262144UL) {
                    if (value > (uint32_t)524288UL) {
                        return (uint32_t)524288UL;
                    } else {
                        return (uint32_t)262144UL;
                    }
                } else {
                    if (value > (uint32_t)131072UL) {
                        return (uint32_t)131072UL;
                    } else {
                        return (uint32_t)65536UL;
                    }
                }
            }
        }
    } else {
        if (value > (uint32_t)256U) {
            if (value > (uint32_t)4096U) {
                if (value > (uint32_t)16384U) {
                    if (value > (uint32_t)32768U) {
                        return (uint32_t)32768U;
                    } else {
                        return (uint32_t)16384U;
                    }
                } else {
                    if (value > (uint32_t)8192U) {
                        return (uint32_t)8192U;
                    } else {
                        return (uint32_t)4096U;
                    }
                }
            } else {
                if (value > (uint32_t)1024U) {
                    if (value > (uint32_t)2048U) {
                        return (uint32_t)2048U;
                    } else {
                        return (uint32_t)1024U;
                    }
                } else {
                    if (value > (uint32_t)512U) {
                        return (uint32_t)512U;
                    } else {
                        return (uint32_t)256U;
                    }
                }
            }
        } else {
            if (value > (uint32_t)16U) {
                if (value > (uint32_t)64U) {
                    if (value > (uint32_t)128U) {
                        return (uint32_t)128U;
                    } else {
                        return (uint32_t)64U;
                    }
                } else {
                    if (value > (uint32_t)32U) {
                        return (uint32_t)32U;
                    } else {
                        return (uint32_t)16U;
                    }
                }
            } else {
                if (value > (uint32_t)4U) {
                    if (value > (uint32_t)8U) {
                        return (uint32_t)8U;
                    } else {
                        return (uint32_t)4U;
                    }
                } else {
                    if (value > (uint32_t)2U) {
                        return (uint32_t)2U;
                    } else {
                        if (value > (uint32_t)1U) {
                            return (uint32_t)1U;
                        } else {
                            return (uint32_t)0U;
                        }
                    }
                }
            }
        }
    }
}

具有分支预测和推测执行的现代CPU,对这样的构造进行预测或推测很差。在32位微控制器上,最高位设置扫描版本通常更快,而在8位和16位微控制器上,比较速度特别慢(因为这些值实际上由多个本机字组成)。但谁知道,也许存在一种硬件架构,在32位立即寄存器比较中具有快速条件跳转;这很可能是这种架构上最快的。

对于具有较大参数范围但结果范围较小的更复杂函数,具有参数范围端点(具有可选的匹配结果数组)和二进制搜索的查找表通常是非常快速的选择。对于手头的功能,我不会使用这种方法。

那么,其中哪一个最快? 取决于。

以上示例并非唯一可能的方式,只是一些相对快速的方法。如果参数范围更小或更大,我可能会有不同的例子。如果参数不是整数而是浮点数,则可以使用frexp()来获得2的幂;如果结果恰好为1,则将功率减1。在使用IEEE-754二进制32或二进制64格式的体系结构中,并且参数存储在内存中,只需将参数类型化为无符号整数,并屏蔽掉某些位,就可以获得2的幂。

有一些原因导致某些库(英特尔MKL,AMD核心数学库)甚至操作系统内核在运行时选择最佳(最快)版本的函数。