简单的自制二进制搜索算法被std::binary_search
(再次)击败:
// gcc version 4.8.2 X86_64
#ifndef EXAMPLE_COMPARE_VERSION
# define EXAMPLE_COMPARE_VERSION 0
#endif
static const long long LOOPS = 0x1fffffff;
#include <cassert>
#include <cstdlib>
#include <ctime>
#include <cstdio>
#if EXAMPLE_COMPARE_VERSION
#include <algorithm>
inline bool stl_compare(const int l, const int r) {
return l < r;
}
#else
inline bool compare(const int *beg, const int *end, int v) {
for (const int *p; beg <= end;) {
p = beg + (end - beg) / 2;
if (*p < v) beg = p + 1;
else if (*p > v) end = p - 1;
else return true;
}
return false;
}
#endif
int main() {
const int arr[] = {
1784, 1785, 1787, 1789, 1794, 1796, 1797, 1801,
1802, 1805, 1808, 1809, 1912, 1916, 1918, 1919,
1920, 1924, 1925, 1926, 1929, 1930, 2040, 2044,
2047, 2055, 2057, 2058, 2060, 2061, 2064, 2168,
2172, 2189, 2193, 2300, 2307, 2309, 2310, 2314,
2315, 2316, 2424, 2429, 2432, 2433, 2438, 2441,
2448, 2552, 2555, 2563, 2565, 2572, 2573, 2680,
2684, 2688, 2694, 2697, 2699, 2700, 2704, 2705,
2808, 2811, 2813, 2814, 2816, 2818, 2822, 2826,
2827, 2828, 2936, 2957, 3064, 3070, 3072, 3073,
3074, 3075, 3076, 3077, 3078, 3081, 3082, 3084,
3085, 3086, 3088, 3192, 3196, 3198, 3200, 3205,
3206, 3211, 3212, 3213, 3326, 3327, 3328, 3330,
3331, 3333, 3337, 3338, 3339, 3344, 3448, 3449,
3451, 3452, 3454, 3459, 3461, 3462, 3465, 3469,
3472, 3578, 3585, 3588, 3593, 3594, 3704, 3712,
3715, 3722, 3723, 3852, 3972, 3973, 3974, 3980,
3982, 4088, 4090, 4091, 4092, 4094, 4096, 4098,
4099, 4100, 4101, 4102, 4103, 4105, 4106, 4107,
4108, 4109, 4110, 4216, 4220, 4222, 4223, 4224,
4226, 4227, 4229, 4230, 4233, 4234, 4235, 4238,
4240, 4350, 4354, 4361, 4369, 4476, 4480, 4486,
4600, 4614, 4735, 4864, 4870, 4984, 4991, 5004,
};
clock_t t = clock();
const size_t len = sizeof(arr) / sizeof(arr[0]);
for (long long i = 0; i < LOOPS; i++) {
int v = arr[rand() % len];
#if EXAMPLE_COMPARE_VERSION >= 2
assert(std::binary_search(arr, arr + len, v, stl_compare));
#elif EXAMPLE_COMPARE_VERSION
assert(std::binary_search(arr, arr + len, v));
#else
assert(compare(arr, arr + len, v));
#endif
}
printf("compare version: %d\ttime: %zu\n",
EXAMPLE_COMPARE_VERSION, (clock() - t) / 10000);
}
t.cc
)g++ t.cc -O3 -DEXAMPLE_COMPARE_VERSION=0 -o t0
g++ t.cc -O3 -DEXAMPLE_COMPARE_VERSION=1 -o t1
g++ t.cc -O3 -DEXAMPLE_COMPARE_VERSION=2 -o t2
./t2 ; ./t0 ; ./t1
在我的机器上输出(时间越短越快):
compare version: 2 time: 3533
compare version: 0 time: 4074
compare version: 1 time: 3968
将EXAMPLE_COMPARE_VERSION
设置为0
时,我们使用自制的二进制搜索算法。
inline bool compare(const int *beg, const int *end, int v) {
for (const int *p; beg <= end;) {
p = beg + (end - beg) / 2;
if (*p < v) beg = p + 1;
else if (*p > v) end = p - 1;
else return true;
}
return false;
}
将EXAMPLE_COMPARE_VERSION
设置为1
时,我们会使用:
template <class ForwardIterator, class T>
bool binary_search (ForwardIterator first, ForwardIterator last,
const T& val);
将EXAMPLE_COMPARE_VERSION
设置为2
时,我们会使用:
template <class ForwardIterator, class T, class Compare>
bool binary_search (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
// the Compare function:
inline bool stl_compare(const int l, const int r) {
return l < r;
}
两个std::binary_search
函数在gcc头文件目录的bits/stl_algo.h
中定义。
std::binary_search
使用比较功能(t2)比没有它的版本(t1)快得多?更新
将random()
替换为rand()
,另请参阅What difference between rand() and random() functions?
答案 0 :(得分:2)
因为基准存在缺陷。
random
:不仅运行时可疑(并影响基准),而且还意味着您可能无法测量基准测试中的相同运行现在,即使清除了废墟,您最终也可能会遇到标准算法比您自己的自制解决方案更快的情况。在这一点上,考虑一下C ++的哲学:你不会为你不需要的东西买单。因此,标准实现很可能,如果不是优化的话,至少足够精简和天真的方法一样快:如果不是这样,那么它们已被修补了很长时间很久以前!
所以,最后,你要留意检查差异。此时,您需要深入研究代码并了解它的映射方式。我建议使用源代码,LLVM IR或汇编进行此探索(如果您不了解某些转换,请随时提出问题)。
也许正在进行一些展开?也许测试更好暗示?谁知道,经过几十年的存在,你可能会找到一颗珍珠。
注意:要在http://coliru.stacked-crooked.com上获取LLVM IR,请使用以下命令行clang -O3 -S -emit-llvm -o main.ll main.cpp && cat main.ll
答案 1 :(得分:1)
对此没有明确的答案,但我可以尝试给出一些观点。
如果指定stl_compare,首先std :: binary_search会调用stl_compare,然后调用实际运算符&lt;引起额外的通话。否则,它只需调用operator
您的算法有机会改善。例如,您在比较时取消引用p 2次。您可以将* p保存为const或寄存器或const寄存器类型以加快速度。
请您修改比较功能,如下所示,试一试
inline bool compare(const int *beg, const int *end, int v) {
while (beg <= end) {
const int *const p = beg + ((end - beg) >> 1);
const int z = *p;
if (z < v) beg = p + 1;
else if (z > v) end = p - 1;
else return true;
}
return false;
}
它在我的机器上显示比stl二进制搜索更好的结果。 (gcc 4.6.3)
修改强>
完全尊重Matthieu M.的评论。我试图重新搜索您的搜索食谱。在我的设置中,我仍然得到与stl。
相当的结果$ ./t0
compare version: 0 time: 3088
$ ./t1
compare version: 1 time: 3113
$ ./t2
compare version: 2 time: 3115
$ ./t0;./t1;./t2
compare version: 0 time: 3082
compare version: 1 time: 3116
compare version: 2 time: 3042
按照修改后的功能使用
inline bool compare(const int *beg, const int *end, int v) {
if(end <= beg) return false; // Comment this line if you are sure end is always greater than beg
int count = end - beg;
while (count > 0) {
const int half = count >> 1;
const int *const p = beg + half;
if (*p < v) {
beg = p + 1;
count -= half + 1;
} else {
count = half;
}
}
return *beg == v;
}
答案 2 :(得分:1)
编译器无论如何都会内联比较函数,而STL实现通常由大师编写。
答案 3 :(得分:1)
我修改了Mohit Jain的第一个答案。我有两个版本:
版本1
inline bool compare(const int *beg, const int *end, int v) {
while (beg <= end) {
const int* const p = beg + ((end - beg) >> 1);
const int z = *p;
if(z != v){
beg = z > v ? beg : p + 1;
end = z < v ? end : p - 1;
}
else {
return true;
}
}
return false;
}
版本2
inline bool compare(const int *beg, const int *end, int v) {
while (beg <= end) {
const int* const p = beg + ((end - beg) >> 1);
const int z = *p;
if(z != v){
beg = z > v ? beg : p;
end = z < v ? end : p;
}
else {
return true;
}
}
return false;
}
运行时间
原始版本:2642
Mohits版本:2435
我的版本1:2413
我的版本2:2366
t1:2606
t2:2508
我使用的gcc版本是4.7.2。
t0,t1和t2的结果与OP结果一致。
对我来说,我的第2版是最快的。这可能是由于过度拟合(即优化该特定测试集的代码)而偶然发生的。另外,我不确定为什么我的版本1比Mohits版本更快。
我测试了各种各样的东西,并认为我应该发布快速的版本。要确定某个版本更快的原因,应检查汇编代码。