c中的二进制搜索优化?

时间:2008-12-10 17:42:23

标签: c binary-search

我正在写一个关键记录查找我在键和rec号之间有一个索引。这是按键排序的。有没有比我的速度优化更好的做到这一点?

typedef struct
{
    char key[MAX_KEYLEN];
    int  rec;
} KeyRecPair;

typedef struct
{
    KeyRecPair *map;
    int         numRecs;
} KeyRecMap;

int GetRecFromKey(char *key, KeyRecMap *theMap)
{
    int cmpValue, bottom = 0;
    int half = theMap->numRecs / 2;
    int top = theMap->numRecs - 1;

    while (bottom != top)
    {
        cmpValue = strncmp(key, theMap->map[half].key, MAX_KEY_LEN); 

        if (cmpValue > 0)
        {
            /*top stays*/
            bottom = half + 1;
            half  = bottom + (top - bottom) / 2;
            continue;
        }
        if (cmpValue < 0)
        {
            /*bottom stays*/
            top = half - 1;
            half  = bottom + (top - bottom) / 2;
            continue;
        }
        return theMap->map[half].rec;
    }

    if (0 == strncmp(key, theMap->map[half].key, MAX_KEY_LEN))
        return theMap->map[half].rec;
    return 0;
}

9 个答案:

答案 0 :(得分:4)

bsearch库函数在给定您实现的合适比较函数的情况下对数组执行二进制搜索。作为一个库函数,它经过了很好的优化,并且(希望)没有错误。

答案 1 :(得分:4)

你的大部分时间将花在strncmp上。

我建议将其强制为inlined,或者将其重新内联,以避免函数调用。

如果你感到勇敢,可以unroll the loop一次或两次看到性能提升。

如果你的字符串实际上是一个固定长度的char数组,你可以使长度为4的倍数,并且一次比较4个字节和unsigned int比较,而不是一次比较1个字节。

如果你没有profiler,你应该得到一个。 Profilers可以轻松查看各种实现的相对成本。

另一种选择是选择一种不同的方式来组织数据。查看AVL trees获取灵感。选择某种Hashing函数,就像提到的其他函数一样,可能是一个可行的选择

答案 2 :(得分:2)

哈希映射可能更合适,因为它具有O(1)查找特性,而不是使用二进制搜索来定位项目。然而,对于天真的方法来说,碰撞加载可能会很慢。但是this paper描述了一种创建类似树的散列映射的方法,该散列映射具有O(log(n)/ log(32))访问时间,通常优于正常的散列映射实现。 (固定的aray +链表实现)。

答案 3 :(得分:1)

你有可能使用不是字符串的密钥吗?或者至少可能是最短的字符串? (什么是MAX_KEYLEN的值)strcmp循环的每次迭代可能是搜索的较慢部分之一。

答案 4 :(得分:1)

有理由想要优化这个吗?您是否使用分析器运行程序并确定查找占总运行时间的很大一部分?你只是好奇你能获得多快的速度吗? (在我看来,这两者都是很好的理由。)如果你只是随机优化它,那就不要了。

另外,请记住基准测试。在现代系统中很难分辨出两个版本的功能中哪一个更快(在我的Z80上更容易)。有多少缓存未命中可能会或可能不会比错误预测的分支数量更重要。

答案 5 :(得分:0)

而不是除以2 U可以利用位移算子。

=&GT; for / 2你可以使用&gt;&gt; 1

答案 6 :(得分:0)

虽然我希望有一个不错的优化器为你做这个,但是我将map-&gt; map放到一个本地,这样它就有一半的机会在一个寄存器中结束,而不是在每次访问时取消引用它。同样,我希望优化器能够为您执行此操作,因此您可能还需要检查程序集输出。

我在发布时查看了visual studio 2008的输出,它在代码上做得非常好。例如,比较代码如下所示:

; 30   :         if (cmpValue > 0)
test    eax, eax
jle SHORT $LN11@GetRecFrom
; 31   :         {
; omitted inner block for > case.
$LN11@GetRecFrom:
; 37   :         if (cmpValue < 0)
jge SHORT $LN2@GetRecFrom

基本上,它是分支而不重新测试cmpValue。很好的接触。

将地图 - &gt;地图放入本地有一点点好处,但它很小。如果MAX_KEY_LEN不是4的很好的倍数并且结构没有填充,那么你应该首先将int放在你的结构中。

答案 7 :(得分:0)

由于你每个循环必须计算一次half,为什么不在它使用之前就这样做一次呢?这将削减两个复杂的(至少相对来说)代码行。

答案 8 :(得分:0)

我能想到的唯一可能的优化是在计算half时使用类似于黄金比率的东西,而不是将剩余的子集分成具有相同数量元素的两半,即

        if (cmpValue > 0)
        {
                /*top stays*/
                bottom = half + 1;
                half = bottom + (top - bottom) * 3 / 5;
                continue;
        }
        if (cmpValue < 0)
        {
                /*bottom stays*/
                top = half - 1;
                half = bottom + (top - bottom) * 2 / 5;
                continue;
        }
豫ICP备18024241号-1