使用CPU缓存行打败二进制搜索

时间:2015-09-01 09:39:27

标签: c++ performance c++11 binary-search cpu-cache

出于教育目的,我试图使用CPU缓存行来击败binary search

https://github.com/nmmmnu/beating_binsearch/blob/master/improved.h

如果您取消注释#define EXIT_ONLY,搜索就像普通binary search一样,除非元素很少,否则搜索会变为linear search

正如预期的那样,它比binary search执行更快

但是我想改善未来,所以如果你评论#define EXIT_ONLY,那么"小"制作linear search而不只是访问"中间"}元件。

理论上,线性搜索的值必须在CPU缓存行中,并且访问必须是#34;免费"。

但实际上,这种搜索方式比正常binary search慢得多。

如果我将CACHE_COUNT_2硬编码为等于1,则速度可比,但仍然较慢。

注意我从未尝试在_linear()中展开for循环。

什么可以解释执行速度较慢?

所有文件的回购都在这里:
https://github.com/nmmmnu/beating_binsearch

2 个答案:

答案 0 :(得分:3)

您的代码中存在一些问题。例如,此代码不考虑缓存行边界。

while (end - start > CACHE_COUNT_MIN){
//  uint64_t mid = start + ((end - start) /  2);
uint64_t mid = start + ((end - start) >> 1);

等...

char cmp = _linear(mid - CACHE_COUNT_2, mid + CACHE_COUNT_2, data, key, mid);

缓存行分配在以行大小为模的地址上。因此,要扫描整个缓存行,您需要屏蔽掉地址的相关位。即使它是缓存命中,你仍然会花费访问该行的周期(越多,它在层次结构中)。

二进制搜索已经是用于基于比较的搜索的更高效缓存算法之一,但是通过缓存感知来改进它可能是困难的。您在每次迭代时消除了一半的搜索空间,这已经避免了大多数缓存未命中,并且它是一个线性空间,并且每次搜索都会增加局部性。预测甚至可以隐藏一些未命中。

您可能希望使用perf对代码中的效果事件进行抽样。此外,为了了解缓存感知有时如何用于优化算法,您可能还需要查看一些现有的知识库,例如hopscotch hashing

答案 1 :(得分:2)

我做了展开的搜索版本,

https://github.com/nmmmnu/beating_binsearch/blob/master/improved_unroll.h

这是有问题的代码:

char search(uint64_t const start1, uint64_t const end1, const T *data, const T key, uint64_t &index) const{
    /*
     * Lazy based from Linux kernel...
     * http://lxr.free-electrons.com/source/lib/bsearch.c
     */

    uint64_t start = start1;
    uint64_t end   = end1;

    char cmp = 0;

    //while (start < end){
    while (start < end){
    //  uint64_t mid = start + ((end - start) /  2);
        uint64_t mid = start + ((end - start) >> 1);

        //char cmp = _linear(mid - CACHE_COUNT_2, mid + CACHE_COUNT_2, data, key, mid);

        #define _LINE_HALF_SIZE 7
        #define _LINE(i)                        \
        if (i >= end){                          \
            start = mid + _LINE_HALF_SIZE + 1;  \
            continue;                           \
        }                                       \
                                                \
        cmp = comp.cmp(data[i], key);           \
                                                \
        if (cmp == 0){                          \
            index = i;                          \
            return 0;                           \
        }                                       \
                                                \
        if (cmp > 0){                           \
            end = i + 1;                        \
            continue;                           \
        }

        _LINE(mid - 7);
        _LINE(mid - 6);
        _LINE(mid - 5);
        _LINE(mid - 4);
        _LINE(mid - 3);
        _LINE(mid - 2);
        _LINE(mid - 1);
        _LINE(mid    );
        _LINE(mid + 1);
        _LINE(mid + 2);
        _LINE(mid + 3);
        _LINE(mid + 4);
        _LINE(mid + 5);
        _LINE(mid + 6);
        _LINE(mid + 7);

        #undef _LINE

        start = mid + _LINE_HALF_SIZE + 1;
    }

    index = start;
    return cmp;
}

似乎有太多的分支错误预测,因为如果我删除了以下if语句:

        if (i >= end){                          \
            start = mid + _LINE_HALF_SIZE + 1;  \
            continue;                           \
        }                                       \

速度“神奇地”变得与classical binary search相同甚至更好 - 当然,因为我删除了分支,算法没有真正正常工作,但这清楚地表明为什么算法慢于{{1} }。