用于一次性查找但多次遍历和访问的容器的 C++ 有效方法

Posted

技术标签:

【中文标题】用于一次性查找但多次遍历和访问的容器的 C++ 有效方法【英文标题】:C++ effective approach to container for one-time lookup but multiple traverses and accesses 【发布时间】:2021-02-01 15:10:56 【问题描述】:

从我的问题中的元素构建数组/向量时,我需要检查它们是否尚未插入,即不允许重复。我可以使用 set/map/unordered_map,而不是使用数组/向量。但是,我想维护使 set/map 无法使用的插入顺序,并且在我用数据填充这个容器之后,我只想要访问和能够尽可能快地遍历容器。 unordered_map 已摊销查找,并且在 O(1) 处遍历步骤,但不能保证。所以我的解决方案是创建vector和unordered_map,将元素添加到两者中,使用unordered_map来检查是否应该将元素插入到两者中,并且进一步只使用vector。这有什么意义还是有更方便有效的方法?

【问题讨论】:

@Ron 当然,但我的问题更多是关于如何解决这种情况以从中获得最佳结果,如果有办法做得更好的话。我考虑的另一个选择是仅使用 unordered_map,这样我就不会花费额外的内存,也不必插入两次,但我认为按照我描述的方式进行操作会更好。 我们所说的向量有多大?对包含数百个项目的向量进行线性搜索确实非常快。 我认为尝试这种方法很有意义,但要知道哪种方法更好,唯一真正的方法是测量。 @NathanOliver 在大多数情况下在成百上千个元素之间,在极端情况下有数万个(估计)。但是,我对创建的集群多次执行“一次查找和多次访问/遍历”循环。 @super 是的,回想起来我可以做到这一点,但目前我手头没有实际的实时实例,所以我想知道它背后的想法是否合理,因为它看起来有点 hacky。跨度> 【参考方案1】:

这有什么意义还是有更方便有效的方法?

这通常很有意义,并且会以最佳方式扩展,但对于所描述的问题,您只需要 unordered_set 来跟踪已插入的元素,而不是带有一些任意/不相关值的 unordered_map (例如true)。

如果需要进一步优化...

当元素的数量很少(例如数百个,但做一些基准来计算出适用于您的数据的确切阈值)时,请考虑仅使用向量和蛮力搜索重复项;如果您可以从您的密钥中廉价地生成高质量的哈希值,那么可以使用bloom filter 进行优化,因此您很少对以前看不见的元素进行暴力搜索(如果重复的元素相对不寻常,那么布隆过滤器甚至可能是咨询unordered_set之前有用的第一步)

如果您知道预期有多少元素 - 使用 unordered_set::reserve()vector::reserve() 在插入元素之前。

您可以使用更快的哈希表。对于较小的键,并且仅按照您的描述进行插入时,封闭散列/开放寻址往往比 unordered_set 使用的单独链接快得多。你可以找到一个相当近期的 OSS 哈希表比较here。

【讨论】:

感谢您的深入分析,尤其是指出 unordered_set。使用地图让我感觉很尴尬,但不知何故,我并没有想到要寻找更合适的数据结构。【参考方案2】:

很难用 O(1) 的查找来制作容器(除了表单数组)。会有这样的权衡。哈希图/表也不例外。但是,如果您调整 std::unordered_map 的存储桶大小,则在处理大量数据时它可能会非常快。但是您仍然在谈论纳秒。

CryEngine 使用的一种在大多数情况下比传统的std::unordered_map 更快的方法是,它们使用std::vector 来存储条目。这意味着它比无序地图使用的链接表现更好。这提高了局部性,从而减少了缓存未命中。他们这样做的方式是,向量像一个集合一样工作。插入条目后,它会对其进行排序,并在查找时进行二进制搜索。这确实工作得很快。

另一种选择是 Google Sparse Hash,据说它是最有效的哈希表之一。另一个是 EA 的标准库hash table。这些只是众多中的一小部分。

就您而言,在我们对其进行分析之前,我们无法确定。但需要注意的是,如果您要同时向 std::unordered_mapstd::vector 插入元素,那么您谈论的是两个单独的 new 调用(但这取决于)。它可能对您没有好处(插入时)。

【讨论】:

感谢您提供有关替代方案的信息和提示。我不确定我是否正确理解了 CryEngine 的描述,但我无法排序,因为排序包含我的问题中的信息。 @eXPRESS 我对 CryEngine 所说的是,他们使用排序向量,它比无序映射具有更好的插入和查找。如果你可以排序,那么它不是一个选项。

以上是关于用于一次性查找但多次遍历和访问的容器的 C++ 有效方法的主要内容,如果未能解决你的问题,请参考以下文章

C ++:用于大型矢量/双端队列的容器替换

C++拾趣——STL容器的插入删除遍历和查找操作性能对比(Windows VirtualStudio)——遍历和删除

C++拾趣——STL容器的插入删除遍历和查找操作性能对比(Windows VirtualStudio)——删除

C++拾趣——STL容器的插入删除遍历和查找操作性能对比(ubuntu g++)——删除

C++拾趣——STL容器的插入删除遍历和查找操作性能对比(Windows VirtualStudio)——插入

C++拾趣——STL容器的插入删除遍历和查找操作性能对比(ubuntu g++)——插入