Most efficient search on small array?
我有一个C数组,它很少(几乎从不)更新:
1 | unsigned long user_values[8]; |
我希望能够进行大量的快速查找(在速度较慢的机器上每秒数百次),以检查数组中是否存在值,如果存在,则获取其索引。几乎所有时候,此查找都无法在数组中找到项。
通常,我会保持数组的排序,并使用二进制搜索,但我不确定在非常小的数据集上进行二进制搜索的效率。有没有更有效的方法来进行这种查找,考虑到它的小,已知的大小?
我建议使用类似于小散列的方法来立即检测大多数故障。像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #define HASH(x) ((x) & 0xff) // This can be improved if needed uint8_t possible[256]; void initHash() { int i; memset(possible, 0, sizeof(possible)); for (i=0;i<8;i++) possible[HASH(user_values[i])] = 1; } int find(unsigned long val) { // Rule out most failures with a quick test. if (!possible[HASH(val)]) return -1; // Now use either binary or linear search. ... } |
请注意,在256插槽哈希表中最多设置8个插槽,您将立即排除31/32或97%的故障。有三种明显的方法可以改善这一点:
这个程序执行二进制搜索和线性搜索(很多次这样他们可以很容易地定时)。在通常找不到搜索值的条件下,线性搜索大约是二进制搜索的两倍。二值搜索需要3次迭代才能将8个元素减少到1,而线性搜索需要8次迭代。我用的是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | #include <stdio.h> #include <time.h> #define ARRLEN 8 #define LOOPS 0x7FFFFF unsigned user_values[ARRLEN] = { 1, 234, 8124, 8335, 10234, 11285, 637774, 788277 }; int main (int argc, char *argv[]) { unsigned val; int bot, top, mid, loop; clock_t start, elap; if (argc < 2) return 1; if (sscanf (argv[1],"%u", &val) != 1) return 1; // binary search printf ("Binary search for %u:", val); start = clock(); for (loop=0; loop!=LOOPS; loop++) { bot = 0; top = ARRLEN; while (top-bot > 1) { mid = ((top + bot) >> 1); if (user_values[mid] <= val) bot = mid; else top = mid; } } elap = clock() - start; if (user_values[bot] == val) printf ("found"); else printf ("not found"); printf (" in count %u ", (unsigned)elap); // linear search printf ("Linear search for %u:", val); start = clock(); for (loop=0; loop!=LOOPS; loop++) { for (bot=0; bot<ARRLEN; bot ++) if (user_values[bot] == val) break; } elap = clock() - start; if (bot<ARRLEN) printf ("found"); else printf ("not found"); printf (" in count %u ", (unsigned)elap); return 0; } |
对于
1 2 3 4 5 6 7 8 9 10 11 12 13 | #define NOT_FOUND -1 int index = NOT_FOUND; // or any other way to mark that number is not found switch (val) { case 0x00000000UL : // replace with array values index = 0; break; case 0x00000001UL : index = 1; break; case 0x00000002UL : index = 2; break; // ... }; |
唯一的缺点是数字现在在编译时是"固定的"。因此,要更新它们,您需要重新编译整个程序,什么是不可接受的(?).
你想加快多少速度?除非在你的集合(数组)中有一些可利用的模式,只有8个检查,否则任何一种方法都将获得最小的收益。编译器通常非常擅长优化这类事情。我发现,通过一些gcc编译,在for循环中使用指针可以为我购买百分之几。展开循环,因为您知道静态8偏移值可能值几个百分点(也可能不值)。
这里是一个假设的可利用数据模式。如果您的集合/向量/列表/数组/调用它们您倾向于集群,并且您要测试的候选范围在0x00000000到0xffffffff范围内均匀分布,那么您可能会获得一点向量预排序,并简单地测试小于第一个或大于最后一个,这将是2 TE。STS通常会失败,如果失败,则会通过列表进行线性搜索。但这种情况实际上取决于不同的比例(窗户有多宽?预分带会增加多少开销?等)。只有根据真实数据进行测试才能知道。
而且总是存在这样一种真正的危险:可利用的模式,虽然通常会让你加速20%,但在你的假设被违反的边缘情况下,会表现得非常糟糕,以数量级伤害你。