【问题标题】:Beating binary search using CPU cache line使用 CPU 缓存线击败二进制搜索
【发布时间】:2015-11-26 11:49:45
【问题描述】:

出于教育目的,我正在尝试使用 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 缓存行中,并且访问必须是“免费的”。

但实际上,这种搜索比普通的binary search 慢得多。

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

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

执行速度较慢的原因是什么?

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

【问题讨论】:

  • 我想你可能会在codereview.stackexchange.com得到更好的答案
  • @user2079303 你可以,但这个问题必须首先完全重新完成。 CodeReview 上的问题需要 a) 已经按预期工作,并且 b) 问题中嵌入了代码。此外,功能请求和代码解释也是题外话。
  • 如果您只包含指向您的存储库的链接而不是在您的问题中内联相关摘录,我认为您不会得到很多答案。
  • 我想我不明白在中间添加小搜索应该如何提高速度的想法。在中间做一个小的线性搜索有什么意义?当您接近目标时这是有道理的,但在所有其他情况下,花费几乎免费的周期是毫无意义的。假设第一个比较告诉您需要走到中间的左侧。在中间右侧进行线性搜索有什么意义?
  • 我希望预取器能够理解二进制搜索并主要消除缓存未命中。二进制搜索并不像看起来那样对缓存不友好。

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


【解决方案1】:

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

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

【讨论】:

  • "缓存行分配在以行大小为模的地址上" - 我不太清楚,请您详细说明一下,并可能给我一些简单的代码
  • 缓存行的起始地址只能是行大小的倍数。因此,起始地址始终采用(line_size x n) 的形式。这意味着您可以屏蔽相关位以获取缓存行的起始地址(例如(line_size >> 3) - 1
  • 所以对于 64 字节的缓存行,为了找到行的开头,我需要“清除”6 个最低有效位,对吗?
  • 是的,六位 (line_size - 1)。抱歉,请忽略以上内容。
  • @Nick 这是一篇link 对优化缓存对齐的更深入的文章。
【解决方案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 相同甚至更好 - 当然,因为我消除了算法并没有真正正确运行的分支,但这清楚地表明了为什么算法比 classical binary search 慢。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-20
    • 1970-01-01
    • 2015-07-23
    相关资源
    最近更新 更多