整数二进制搜索的极端优化

时间:2011-07-07 20:11:38

标签: algorithm optimization micro-optimization

我正在编写一个程序,需要进行大量的二进制搜索 - 至少10个 15 - 紧密循环。这些以及少量的按位操作将占程序运行时的75%以上,因此使它们快速非常重要。 (正如现在实施的那样,它占用了95%以上的时间,但是我正在使用一种非常不同的实现[不是搜索],我正在替换它。)

要搜索的数组(当然,它不需要实现为数组)非常小。在我目前的情况下,它由41个64位整数组成,但用于优化其他大小的数组的技术将是有用的。 (我之前遇到过类似的问题。)

我可以提前分析数据,以确定最有可能的范围以及匹配的频率。收集这些信息并不容易,但我应该在一天结束时使用它。

我的代码可能在C中使用内联汇编;它将使用最新版本的gcc进行编译。欢迎任何语言的回复;如果您愿意(例如)FORTRAN,我可以翻译。

那么:我如何有效地实现这种搜索?

澄清:我实际上是使用搜索来测试成员资格,而不是使用数组中的位置。丢弃该信息的解决方案是可以接受的。


最终代码:

long ispow3_tiny(ulong n)
{
    static ulong pow3table[] = {
#ifdef LONG_IS_64BIT
        12157665459056928801, 0, 4052555153018976267, 1350851717672992089, 0, 450283905890997363, 150094635296999121, 0, 50031545098999707, 0, 16677181699666569, 5559060566555523, 0, 1853020188851841, 617673396283947, 0, 205891132094649, 0, 68630377364883, 22876792454961, 0, 7625597484987, 2541865828329, 0, 847288609443, 282429536481, 0, 94143178827, 0, 31381059609, 10460353203, 0,
#endif
        3486784401, 1162261467, 0, 387420489, 0, 129140163, 43046721, 0, 14348907, 4782969, 0, 1594323, 531441, 0, 177147, 0, 59049, 19683, 0, 6561, 2187, 0, 729, 0, 243, 81, 0, 27, 9, 0, 3, 1
    };

    return n == pow3table[__builtin_clzl(n)];
}

4 个答案:

答案 0 :(得分:15)

由于你的价值是三的权力,我认为我们可以大大优化。让我们看看二进制数字:

Columns are P, I, B:
P = Power (3 ^ P)
I = Index of MSB (Most Significant Bit)
B = Binary Value

00 00 0000000000000000000000000000000000000000000000000000000000000001
01 01 0000000000000000000000000000000000000000000000000000000000000011
02 03 0000000000000000000000000000000000000000000000000000000000001001
03 04 0000000000000000000000000000000000000000000000000000000000011011
04 06 0000000000000000000000000000000000000000000000000000000001010001
05 07 0000000000000000000000000000000000000000000000000000000011110011
06 09 0000000000000000000000000000000000000000000000000000001011011001
07 11 0000000000000000000000000000000000000000000000000000100010001011
08 12 0000000000000000000000000000000000000000000000000001100110100001
09 14 0000000000000000000000000000000000000000000000000100110011100011
10 15 0000000000000000000000000000000000000000000000001110011010101001
11 17 0000000000000000000000000000000000000000000000101011001111111011
12 19 0000000000000000000000000000000000000000000010000001101111110001
13 20 0000000000000000000000000000000000000000000110000101001111010011
14 22 0000000000000000000000000000000000000000010010001111101101111001
15 23 0000000000000000000000000000000000000000110110101111001001101011
16 25 0000000000000000000000000000000000000010100100001101011101000001
17 26 0000000000000000000000000000000000000111101100101000010111000011
18 28 0000000000000000000000000000000000010111000101111001000101001001
19 30 0000000000000000000000000000000001000101010001101011001111011011
20 31 0000000000000000000000000000000011001111110101000001101110010001
21 33 0000000000000000000000000000001001101111011111000101001010110011
22 34 0000000000000000000000000000011101001110011101001111100000011001
23 36 0000000000000000000000000001010111101011010111101110100001001011
24 38 0000000000000000000000000100000111000010000111001011100011100001
25 39 0000000000000000000000001100010101000110010101100010101010100011
26 41 0000000000000000000000100100111111010011000000100111111111101001
27 42 0000000000000000000001101110111101111001000001110111111110111011
28 44 0000000000000000000101001100111001101011000101100111111100110001
29 45 0000000000000000001111100110101101000001010000110111110110010011
30 47 0000000000000000101110110100000111000011110010100111100010111001
31 49 0000000000000010001100011100010101001011010111110110101000101011
32 50 0000000000000110100101010100111111100010000111100011111010000001
33 52 0000000000010011101111111110111110100110010110101011101110000011
34 53 0000000000111011001111111100111011110011000100000011001010001001
35 55 0000000010110001101111110110110011011001001100001001011110011011
36 57 0000001000010101001111100100011010001011100100011100011011010001
37 58 0000011000111111101110101101001110100010101101010101010001110011
38 60 0001001010111111001100000111101011101000000111111111110101011001
39 61 0011100000111101100100010111000010111000010111111111100000001011
40 63 1010100010111000101101000101001000101001000111111110100000100001

观察结果是所有值都具有唯一的MSB。

使用x86位扫描指令,我们可以快速确定MSB。

http://www.arl.wustl.edu/~lockwood/class/cs306/books/artofasm/Chapter_6/CH06-4.html#HEADING4-67

使用MSB作为64项表的索引。将表中的值与要检查的相等值进行比较。如果它们不相等,则测试失败。

编辑:j_random_hacker指出最低的8位也都是唯一的。您可能希望实现每个版本并查看哪个版本最快。

答案 1 :(得分:12)

不是你的问题的答案,而是改进的建议:如果你必须做那么多的查询,它可能值得首先计算41个值的完美哈希函数,然后用它来获得索引。 / p>

答案 2 :(得分:4)

Jon Bentley提出的一种非常简单的方法是,将表格大小调整为64并最初用MAXINT填充它,然后执行:

i = 0;
if (key >= a[i+32]) i += 32;
if (key >= a[i+16]) i += 16;
if (key >= a[i+ 8]) i +=  8;
if (key >= a[i+ 4]) i +=  4;
if (key >= a[i+ 2]) i +=  2;
if (key >= a[i+ 1]) i +=  1;
if (a[i]==key){
  // got it !
}

一种肮脏但更快的方式是if-tree:

if (key < a[32]){ // we know i >= 0 && i < 32
    if (key < a[16]){  // we know i >= 0 && i < 16
      // etc. etc.
    } else {           // we know i >= 16 && i < 32
      // etc. etc.
    }
} else {          // we know i >= 32 && i < 64
    if (key < a[48]){  // we know i >= 32 && i < 48
      // etc. etc.
    } else {           // we know i >= 48 && i < 64
      // etc. etc.
    }
}

一个小代码生成器或一串宏可以生成这个树。

答案 3 :(得分:1)

当然,只有真正的分析,真实硬件和真实数据才会给出正确答案,但我认为可能使用基于比较的二进制搜索不会是最快的选择(特别是因为现代CPU讨厌分支)

如果故障数量非常高(仅仅是一个疯狂的猜测,但41比2 ** 64小得多),测试成员资格,那么IMO哈希可能是一个更好的选择,只采用真正的搜索如果传递哈希测试以避免误报。

这个想法是

 for x in interesting_values:
     hmap[hash(x)] = True

 for x in data:
     if hmap[hash(x)]:
         # do a full check here

散列函数可以非常简单(例如,四个16位组的按位xor,在两个步骤中计算为32 xor 32后跟16 xor 16),在这种情况下,散列映射可以是65536位。在这个64k映射中不能设置超过41位,因此如果您的数据是随机分布的,那么完全搜索将很少进行。根据缓存的考虑,可能每个哈希映射单元使用一个字节更好。

也可能使用8位散列甚至更好,但在这种情况下,缓存命中可能是41/256而不是41/65536。