【发布时间】:2021-10-11 19:20:57
【问题描述】:
背景:我是 C++ 新手。我有一个std::map 并且正在尝试按键搜索元素。
问题:性能。当地图变大时,map::find() 函数会变慢。
首选方法:我经常大致知道元素应该在地图的哪个位置;我可以提供一个[first,last) 范围进行搜索。这个范围总是很小的w.r.t。地图中的元素数量。我有兴趣编写一个带有边界提示的简短二进制搜索实用程序函数。
尝试:我从https://en.cppreference.com/w/cpp/algorithm/lower_bound 窃取了以下函数并做了一些粗略的基准测试。无论提供的范围提示的大小或位置如何,对于大大小小的地图,这个函数似乎都比map::find() 慢得多。我将比较语句 (it->first < value) 替换为随机的比较 ints 并且减速似乎得到了解决,所以我认为减速可能是由 it->first 的取消引用引起的。
问题:取消引用是问题吗?或者是否正在进行某种不必要的复制/移动操作?我想我记得读过地图不会将它们的元素节点顺序存储在内存中,所以我只是得到了一堆缓存未命中?减速的可能原因是什么,我将如何解决?
/* @param first Iterator pointing to the first element of the map to search.
* @param distance Number of map elements in the range to search.
* @param key Map key to search for. NOTE: Type validation is not a concern just yet.
*/
template<class ForwardIt, class T>
ForwardIt binary_search_map (ForwardIt& first, const int distance, const T& key) {
ForwardIt it = first;
typename std::iterator_traits<ForwardIt>::difference_type count, step;
count = distance;
while (count > 0) {
it = first;
step = count/2;
std::advance(it, step);
if (it->first < value) {
first = ++it;
count -= step + 1;
}
else if (it->first > value)
count = step;
else {
first = it;
break;
}
}
return first;
}
【问题讨论】:
-
我很久没关注你的问题了,但我看到的很明显的事情是你拨打的
std::advance必须遍历地图。这与地图的设计方式完全不同,地图的设计方式大多是平衡的,并设置为最佳二分搜索。所以你的“猜测”必须比 O(logN) 好得多。好像不是。 -
看看你的标准库的
find是怎么实现的,看看他们是怎么做的。 (众所周知,C++ 难以进行微基准测试,“粗略的基准测试”可能无关紧要。) -
在自己制作之前,请检查 Boost 中是否没有更适合您的用例的集合
-
树迭代器是双向迭代器。双向迭代器上的
std::advance(it, count)在循环中调用++it或--itcount次。树中的operator++需要找到下一个节点,这是一个O(log n)操作,其中n是容器的大小。 -
二进制搜索只有在你有一个随机访问迭代器时才有意义。
std::map不提供随机访问迭代器,所以每次你想得到一个与其他迭代器(大约)相等距离的迭代器时,到达那里的复杂性是Ω(n)这意味着进行二进制搜索至少有相同的复杂性;假设您的比较操作并不昂贵,那么算法将更简单并且可能至少同样有效;提高在字典顺序上接近的字符串的性能,在字符串末尾开始比较可能会提高性能
标签: c++ optimization iterator binary-search stdmap