为什么这个实现的二进制搜索比 std::binary_search() 慢这么多?
Why is this implemented binary search so much slower than std::binary_search()?
在我检测到 std::upper_bound 之前,我实现了我自己的 binarySearch 版本来确定所需元素的索引。该实现有效,但与线性搜索相比,我的 binarySearch 只快了一点点。随着搜索区域的扩大,我的实现与标准库之间的因素也会增加。
为了进行快速自测,我在此 post 末尾插入了完整的代码。在这里快速浏览一下我的 searchBinary 实现:
template<typename T> T searchBinary(const std::vector<std::vector<T> > vectorList, const std::vector<T> compareVector) {
long iteration = 0;
size_t leftIndex = 0;
size_t rightIndex = vectorList.size()-1;
size_t pos;
while (leftIndex <= rightIndex) {
iteration++;
pos = (leftIndex + rightIndex) / 2;
if (compareVector < vectorList[pos]) {
rightIndex = pos - 1;
} else if (compareVector > vectorList[pos]) {
leftIndex = pos + 1;
} else {
cout << "Match at binary search after " << iteration << " iterations.\n";
return pos;
}
}
cout << "No match at binary search after " << iteration << " iterations.\n";
return -1;
}
这就是我确定 运行时间的方式:
void searchBinaryOwn_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
struct timeval begin, end;
long seconds, useconds;
if (gettimeofday(&begin,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
searchBinary(vectorList, compareVector);
if (gettimeofday(&end,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
seconds = end.tv_sec - begin.tv_sec;
useconds = end.tv_usec - begin.tv_usec;
if(useconds < 0) {
useconds += 1000000;
seconds--;
}
printf("searchBinaryOwn(): %ld sec %ld usec\n\n", seconds, useconds);
return;
}
这里没有发现任何问题。如果我 运行 这个程序有 8 000
000 个元素:
- searchLinear() 需要 ~3.7 秒
- searchBinaryOwn() 需要 ~2.8 秒
- searchBinaryStd() 需要 ~0,0007 秒
那么为什么两个二分搜索之间会有如此巨大的差异?(用gcc 4.8.2编译)
注意:由于 "cout..." 大约需要 30 微秒,std::binarySearch 实际上比显示的
快得多
这里是完整代码:
#include <iostream>
#include <vector>
#include <sys/time.h>
#include <algorithm>
#include <string>
#include <stdio.h>
using namespace std;
template<typename T> T searchBinary(const std::vector<std::vector<T> > vectorList, const std::vector<T> compareVector) {
long iteration = 0;
size_t leftIndex = 0;
size_t rightIndex = vectorList.size()-1;
size_t pos;
while (leftIndex <= rightIndex) {
iteration++;
pos = (leftIndex + rightIndex) / 2;
if (compareVector < vectorList[pos]) {
rightIndex = pos - 1;
} else if (compareVector > vectorList[pos]) {
leftIndex = pos + 1;
} else {
cout << "Match at binary search after " << iteration << " iterations.\n";
return pos;
}
}
cout << "No match at binary search after " << iteration << " iterations.\n";
return -1;
}
size_t searchLinear(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
size_t vectorListSize = vectorList.size();
for (size_t i = 0; i < vectorListSize; i++) {
if (vectorList[i] == compareVector) {
return i;
}
}
return (size_t)-1;
}
void searchLinear_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
struct timeval begin, end;
long seconds, useconds;
if (gettimeofday(&begin,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
//search
cout << "\nPos: " << searchLinear(vectorList, compareVector) << endl;
if (gettimeofday(&end,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
seconds = end.tv_sec - begin.tv_sec;
useconds = end.tv_usec - begin.tv_usec;
if(useconds < 0) {
useconds += 1000000;
seconds--;
}
printf("searchLinear(): %ld sec %ld usec\n\n", seconds, useconds);
return;
}
void searchBinaryStd_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
struct timeval begin, end;
long seconds, useconds;
if (gettimeofday(&begin,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
//search
cout << "found: " << std::binary_search(vectorList.begin(), vectorList.end(), compareVector) << endl;
if (gettimeofday(&end,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
seconds = end.tv_sec - begin.tv_sec;
useconds = end.tv_usec - begin.tv_usec;
if(useconds < 0) {
useconds += 1000000;
seconds--;
}
printf("searchBinaryStd(): %ld sec %ld usec\n\n", seconds, useconds);
return;
}
void searchBinaryOwn_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
struct timeval begin, end;
long seconds, useconds;
if (gettimeofday(&begin,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
searchBinary(vectorList, compareVector);
if (gettimeofday(&end,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
seconds = end.tv_sec - begin.tv_sec;
useconds = end.tv_usec - begin.tv_usec;
if(useconds < 0) {
useconds += 1000000;
seconds--;
}
printf("searchBinaryOwn(): %ld sec %ld usec\n\n", seconds, useconds);
return;
}
int main() {
std::vector<u_char> compareVector;
compareVector.clear();
compareVector.push_back(0xF8);
compareVector.push_back(0xD1);
compareVector.push_back(0x11);
compareVector.push_back(0xFF);
std::vector<std::vector<u_char> > vectorList;
vectorList.clear();
std::vector<u_char> temp;
for (unsigned int i = 0; i < ((unsigned int)-1); i++) {
if (i == 8000000) {
// if (i == 15000000) {
break;
}
temp.clear();
temp.push_back(0x11);
temp.push_back(0x22);
temp.push_back(0x33);
temp.push_back(0x44);
vectorList.push_back(temp);
}
vectorList[7999999] = compareVector;
cout << "Elements in vectorList: " << vectorList.size() << endl;
searchLinear_messure(vectorList, compareVector);
searchBinaryStd_messure(vectorList, compareVector);
searchBinaryOwn_messure(vectorList, compareVector);
return 0;
}
好吧,这个 template<typename T> T searchBinary(const std::vector<std::vector<T> > vectorList, const std::vector<T> compareVector)
制作输入向量的副本(当您按值传递它时),它在时间上是线性的。所以你得到的结果实际上是预期的。
顺便说一句。一个半开玩笑的回答可能是标准库是由非常优秀的开发人员编写的,预计几乎不会被超越。
- 将您的函数原型更改为
template<typename T> T searchBinary(const std::vector<std::vector<T> >& vectorList, const std::vector<T>& compareVector) {
即通过常量引用而不是值传递。这将避免两个向量副本。
您可以在每次迭代中使用单个条件测试 <
进行重构。 (您还需要更改 while
条件)。
iteration
必须是 long
吗?不能短点吗?收敛的最坏情况是什么?
第 1 点很重要。 2 非常重要,3 是微优化,在某些系统上可能根本不会产生任何影响。
矢量按值传递给 searchBinary
,因此创建副本需要时间。
如果您将签名更改为
template<typename T> T searchBinary(const std::vector<std::vector<T> >& vectorList, const std::vector<T>& compareVector)
它与标准实现一样快:http://melpon.org/wandbox/permlink/qozapTfn3MrGv5JA
在我检测到 std::upper_bound 之前,我实现了我自己的 binarySearch 版本来确定所需元素的索引。该实现有效,但与线性搜索相比,我的 binarySearch 只快了一点点。随着搜索区域的扩大,我的实现与标准库之间的因素也会增加。
为了进行快速自测,我在此 post 末尾插入了完整的代码。在这里快速浏览一下我的 searchBinary 实现:
template<typename T> T searchBinary(const std::vector<std::vector<T> > vectorList, const std::vector<T> compareVector) {
long iteration = 0;
size_t leftIndex = 0;
size_t rightIndex = vectorList.size()-1;
size_t pos;
while (leftIndex <= rightIndex) {
iteration++;
pos = (leftIndex + rightIndex) / 2;
if (compareVector < vectorList[pos]) {
rightIndex = pos - 1;
} else if (compareVector > vectorList[pos]) {
leftIndex = pos + 1;
} else {
cout << "Match at binary search after " << iteration << " iterations.\n";
return pos;
}
}
cout << "No match at binary search after " << iteration << " iterations.\n";
return -1;
}
这就是我确定 运行时间的方式:
void searchBinaryOwn_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
struct timeval begin, end;
long seconds, useconds;
if (gettimeofday(&begin,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
searchBinary(vectorList, compareVector);
if (gettimeofday(&end,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
seconds = end.tv_sec - begin.tv_sec;
useconds = end.tv_usec - begin.tv_usec;
if(useconds < 0) {
useconds += 1000000;
seconds--;
}
printf("searchBinaryOwn(): %ld sec %ld usec\n\n", seconds, useconds);
return;
}
这里没有发现任何问题。如果我 运行 这个程序有 8 000 000 个元素:
- searchLinear() 需要 ~3.7 秒
- searchBinaryOwn() 需要 ~2.8 秒
- searchBinaryStd() 需要 ~0,0007 秒
那么为什么两个二分搜索之间会有如此巨大的差异?(用gcc 4.8.2编译) 注意:由于 "cout..." 大约需要 30 微秒,std::binarySearch 实际上比显示的
快得多这里是完整代码:
#include <iostream>
#include <vector>
#include <sys/time.h>
#include <algorithm>
#include <string>
#include <stdio.h>
using namespace std;
template<typename T> T searchBinary(const std::vector<std::vector<T> > vectorList, const std::vector<T> compareVector) {
long iteration = 0;
size_t leftIndex = 0;
size_t rightIndex = vectorList.size()-1;
size_t pos;
while (leftIndex <= rightIndex) {
iteration++;
pos = (leftIndex + rightIndex) / 2;
if (compareVector < vectorList[pos]) {
rightIndex = pos - 1;
} else if (compareVector > vectorList[pos]) {
leftIndex = pos + 1;
} else {
cout << "Match at binary search after " << iteration << " iterations.\n";
return pos;
}
}
cout << "No match at binary search after " << iteration << " iterations.\n";
return -1;
}
size_t searchLinear(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
size_t vectorListSize = vectorList.size();
for (size_t i = 0; i < vectorListSize; i++) {
if (vectorList[i] == compareVector) {
return i;
}
}
return (size_t)-1;
}
void searchLinear_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
struct timeval begin, end;
long seconds, useconds;
if (gettimeofday(&begin,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
//search
cout << "\nPos: " << searchLinear(vectorList, compareVector) << endl;
if (gettimeofday(&end,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
seconds = end.tv_sec - begin.tv_sec;
useconds = end.tv_usec - begin.tv_usec;
if(useconds < 0) {
useconds += 1000000;
seconds--;
}
printf("searchLinear(): %ld sec %ld usec\n\n", seconds, useconds);
return;
}
void searchBinaryStd_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
struct timeval begin, end;
long seconds, useconds;
if (gettimeofday(&begin,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
//search
cout << "found: " << std::binary_search(vectorList.begin(), vectorList.end(), compareVector) << endl;
if (gettimeofday(&end,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
seconds = end.tv_sec - begin.tv_sec;
useconds = end.tv_usec - begin.tv_usec;
if(useconds < 0) {
useconds += 1000000;
seconds--;
}
printf("searchBinaryStd(): %ld sec %ld usec\n\n", seconds, useconds);
return;
}
void searchBinaryOwn_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
struct timeval begin, end;
long seconds, useconds;
if (gettimeofday(&begin,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
searchBinary(vectorList, compareVector);
if (gettimeofday(&end,(struct timezone *)0)) {
fprintf(stderr, "can not get time\n");
exit(1);
}
seconds = end.tv_sec - begin.tv_sec;
useconds = end.tv_usec - begin.tv_usec;
if(useconds < 0) {
useconds += 1000000;
seconds--;
}
printf("searchBinaryOwn(): %ld sec %ld usec\n\n", seconds, useconds);
return;
}
int main() {
std::vector<u_char> compareVector;
compareVector.clear();
compareVector.push_back(0xF8);
compareVector.push_back(0xD1);
compareVector.push_back(0x11);
compareVector.push_back(0xFF);
std::vector<std::vector<u_char> > vectorList;
vectorList.clear();
std::vector<u_char> temp;
for (unsigned int i = 0; i < ((unsigned int)-1); i++) {
if (i == 8000000) {
// if (i == 15000000) {
break;
}
temp.clear();
temp.push_back(0x11);
temp.push_back(0x22);
temp.push_back(0x33);
temp.push_back(0x44);
vectorList.push_back(temp);
}
vectorList[7999999] = compareVector;
cout << "Elements in vectorList: " << vectorList.size() << endl;
searchLinear_messure(vectorList, compareVector);
searchBinaryStd_messure(vectorList, compareVector);
searchBinaryOwn_messure(vectorList, compareVector);
return 0;
}
好吧,这个 template<typename T> T searchBinary(const std::vector<std::vector<T> > vectorList, const std::vector<T> compareVector)
制作输入向量的副本(当您按值传递它时),它在时间上是线性的。所以你得到的结果实际上是预期的。
顺便说一句。一个半开玩笑的回答可能是标准库是由非常优秀的开发人员编写的,预计几乎不会被超越。
- 将您的函数原型更改为
template<typename T> T searchBinary(const std::vector<std::vector<T> >& vectorList, const std::vector<T>& compareVector) {
即通过常量引用而不是值传递。这将避免两个向量副本。
您可以在每次迭代中使用单个条件测试
<
进行重构。 (您还需要更改while
条件)。iteration
必须是long
吗?不能短点吗?收敛的最坏情况是什么?
第 1 点很重要。 2 非常重要,3 是微优化,在某些系统上可能根本不会产生任何影响。
矢量按值传递给 searchBinary
,因此创建副本需要时间。
如果您将签名更改为
template<typename T> T searchBinary(const std::vector<std::vector<T> >& vectorList, const std::vector<T>& compareVector)
它与标准实现一样快:http://melpon.org/wandbox/permlink/qozapTfn3MrGv5JA