如何找出哪个是最快的算法

时间:2017-02-08 21:10:01

标签: c# c++ c algorithm

在计算单词中的位数时,蛮力就是这样的:

int CountNumSetBits(unsigned long n)
{
    unsigned short num_setbits = 0;
    while (n)
    {
        num_setbits += n & 1;
        n >>= 1;
    }
    return num_setbits;
}

大O速度为O(n),其中n是Word中的位数。

我想到了另一种编写算法的方法,利用了这样一个事实:我们使用y = x&〜(x-1)来获得第一次出现的设置位

int CountNumSetBitsMethod2(unsigned long n)
{
    unsigned short num_setbits = 0;
    int y = 0;
    while (n)
    {
        y = n& ~(n - 1); // get first occurrence of '1'
        if (y) // if we have a set bit inc our counter
            ++num_setbits;
        n ^=y;  // erase the first occurrence of '1'
    }
    return num_setbits;
}

如果我们假设输入是50%1和50%0,那么第二种算法似乎可以快两倍。但是,实际的复杂性更高:

在方法一中,我们对每个位执行以下操作: 1加 1和 1班

在方法二中,我们为每个设置位执行以下操作: 1和 1补充 1减法(减法的结果必须复制到另一个reg) 1比较 1个增量(如果比较为真) 1 XOR

现在,在实践中,可以通过执行一些分析来确定哪种算法更快。也就是说,使用秒表机制和一些测试数据并调用每个算法一百万次。

然而,我想先做的是看看我能通过眼球代码来估计速度差异(给定相同数量的设置和未设置位)。

如果我们假设减法采用与add(近似)相同的量周期,并且所有其他操作周期相等,那么可以得出结论:每个算法花费大约相同的时间吗?

注意:我在这里假设我们不能使用查找表。

2 个答案:

答案 0 :(得分:2)

第二种算法可以大大简化:

int CountNumSetBitsMethod2(unsigned long n) {
    unsigned short num_setbits = 0;
    while (n) {
        num_setbits++;
        n &= n - 1;
    }
    return num_setbits;
}

有许多方法可以计算单词中设置的位数:

  • 一次使用多个位的查找表
  • 使用64位乘法
  • 使用并行添加
  • 使用额外的技巧剃掉几个周期。

尝试根据经验确定通过计算周期哪个更快是不容易的,因为即使查看汇编输出,也很难评估指令并行化,流水线操作,分支预测,寄存器重命名和争用的影响......现代CPU是非常复杂!此外,生成的实际代码取决于编译器版本和配置,时间取决于CPU类型和版本...更不用说与所使用的特定值集相关的可变性(对于具有可变数量指令的算法)。 / p>

基准测试是一种必要的工具,但即使是仔细的基准测试也可能无法正确模拟实际使用情况。

这是一个很棒的网站,适合这种小小的游戏:

http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetNaive

我建议您在系统上实施不同版本并执行比较基准测试。没有明确的答案,只有特定条件的局部最优。

一些惊人的发现:

// option 3, for at most 32-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) % 
     0x1f;
c += ((v >> 24) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;

更经典的一种,通常被认为是计算32位整数v中的位的最佳方法:

v = v - ((v >> 1) & 0x55555555);                    // reuse input as temporary
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);     // temp
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; // count

答案 1 :(得分:0)

首先,了解事物速度的唯一方法就是衡量它们。

第二 - 找到某些字节中的设置位数,为一个字节中的设置位数构建一个查找表

0->0
1->1
2->1
3->2
4->1

etc.

这是一种常用方法而且非常快

您可以手动编码或在启动时创建