【问题标题】:Binary search of range in std::map slower than map::find() search of whole mapstd::map 中范围的二分搜索比整个地图的 map::find() 搜索慢
【发布时间】: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--it count 次。树中的operator++需要找到下一个节点,这是一个O(log n)操作,其中n是容器的大小。
  • 二进制搜索只有在你有一个随机访问迭代器时才有意义。 std::map不提供随机访问迭代器,所以每次你想得到一个与其他迭代器(大约)相等距离的迭代器时,到达那里的复杂性是Ω(n)这意味着进行二进制搜索至少有相同的复杂性;假设您的比较操作并不昂贵,那么算法将更简单并且可能至少同样有效;提高在字典顺序上接近的字符串的性能,在字符串末尾开始比较可能会提高性能

标签: c++ optimization iterator binary-search stdmap


【解决方案1】:

std::map::find() 的存在是有原因的。该实现已经进行了二进制搜索,因为std::map 有一个平衡二叉树作为实现。

您的二叉搜索实现要慢得多,因为您无法利用二叉树。 如果你想取地图的中间,你可以从std::advance 开始,它取第一个节点(位于树的叶子上)并通过几个指针导航到你认为是中间的地方。之后,您再次需要从这些叶节点之一转到下一个。再次遵循很多指针。

结果:在更多循环旁边,您会遇到很多缓存未命中,尤其是在地图很大时。

如果您想改进地图中的查找,我建议您使用不同的结构。如果订购不重要,您可以使用std::unordered_map。当顺序很重要时,您可以使用已排序的std::vector&lt;std::pair&lt;Key, Value&gt;&gt;。如果你有可用的 boost,它已经存在于一个名为 boost::container::flat_map 的类中。

【讨论】:

  • 我正在考虑根据问题上的一些 cmets 使用不同的数据结构。看起来 boost::container::flat_map 比 map 慢很多,这在很大程度上违背了目的。在此处查看某人最近所做的基准测试:stackoverflow.com/questions/21166675/… Flat_map 迭代更快,正如您所建议的,可能是因为它提供了随机访问迭代器,但搜索仍然比 std::map 慢。对于另一个选项,您如何在成对向量中有效地插入排序?
  • 取决于您的用例。我通常会发现自己创建了一次并查询了很多。几乎没有更新它们,所以如果更新真的很重要,您可能希望延迟排序直到您查询?
猜你喜欢
  • 2014-10-17
  • 1970-01-01
  • 2012-02-05
  • 1970-01-01
  • 2015-08-28
  • 1970-01-01
  • 2010-11-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多