出于教育目的,我试图使用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
答案 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} }。