set与unordered_set进行最快迭代

set vs unordered_set for fastest iteration

本文关键字:set 迭代 unordered      更新时间:2023-10-16

在我的应用程序中,我有以下要求-

  1. 数据结构将只填充一次某些值(而不是键/值对(。这些值可能会重复,但我希望数据结构只存储一次。

  2. 我将在上面创建的数据结构的所有元素中迭代100次。元素在迭代中出现的顺序无关紧要。

约束1表明,由于数据不是键值对的形式,我将不得不使用set或undered_set。

现在,set插入的成本比unsodered_set插入高,但数据结构在程序开始时只填充一次。

我相信决定因素将是我在数据结构的所有元素中迭代的速度。我不确定set还是uncodered_set会更快。我相信标准没有提到这个事实,因为对于任何一个数据结构,这个操作都是O(n(。但我想知道哪种数据结构迭代器.next((会更快。

有几种方法。

  1. 对您的问题的评论建议保留一个具有最快O(1)查找/插入和O(N)迭代的std::unordered_set(就像每个容器一样(。如果您的数据变化很大,或者需要大量随机查找,这可能是最快的。但是测试
  2. 如果您需要在没有中间插入的情况下迭代100次,则可以将单个O(N)复制到std::vector,并从连续内存布局中获得100次测试这是否比常规std::unordered_set更快
  3. 如果在迭代之间有少量的中间插入,那么使用专用向量是值得的。如果您可以使用Boost.Container,请尝试boost::flat_set,它提供了一个带有std::vector存储后端的std::set接口(即一个对缓存和预取非常友好的连续内存布局(。再次,测试这是否会加速其他两个解决方案

对于最后一个解决方案,请参阅Boost文档中的一些权衡(最好注意所有其他问题,如迭代器无效、移动语义和异常安全(:

Boost.Container flat_[multi]映射/设置容器为有序向量基于Austern和Alexandrescu的关联容器指导方针这些有序的矢量容器也从中受益最近在C++中添加了移动语义,加快了速度插入和擦除时间相当长。平面关联容器具有以下属性:

  • 比标准关联容器更快的查找
  • 比标准关联容器更快的迭代
  • 较小对象的内存消耗较少(如果使用了shrink_to_fit,则较大对象的内存占用较少(
  • 提高了缓存性能(数据存储在连续内存中(
  • 非稳定迭代器(插入和擦除元素时迭代器无效(
  • 无法存储不可复制和不可移动的值类型
  • 与标准关联容器相比,异常安全性较弱(复制/移动构造函数在擦除中移位值时可能抛出和插入(
  • 插入和擦除速度比标准关联容器慢(特别适用于不可移动类型(

注意:使用更快的查找,意味着flat_set在连续内存上执行O(log N),而不是O(log N)指针追逐常规std::set。当然,std::unordered_set执行O(1)查找,这对于大的N将更快。

我建议您使用set或unordered_set进行"过滤",完成后,将数据移动到固定大小的向量

如果数据结构的构建没有考虑到性能问题(或至少只是轻微的(,请考虑将数据保存到std::vector中:没有什么比更好的了

为了加快数据结构的初始构建,您可以首先插入到std::unordered_set中,或者在插入之前至少使用一个来检查是否存在。

在第二种情况下,它不需要包含元素,但可以包含例如索引。

std::vector<T> v;
auto h = [&v](size_t i){return std::hash<T>()(v[i]);};
auto c = [&v](size_t a, size_t b){return v[a] == v[b];};
std::unordered_set<size_t, decltype(h), decltype(c)> tester(0, h, c);

无序集合使用哈希表来提供接近O(1(的时间搜索。这是通过使用键的哈希来计算您要查找的元素(键(与数据集开头的偏移量来完成的。除非数据集很小(如chars(,否则不同的密钥可能具有相同的哈希(冲突(。

为了最大限度地减少冲突,无序集必须保持数据存储的稀疏性。这意味着找到一个密钥将是O(1(时间(除非发生冲突(。

然而,当迭代哈希表时,迭代器将在数据存储中遇到大量未使用的空间,这将减慢迭代器查找下一个元素的速度。我们可以用额外的指针链接哈希表中的相邻元素,但我不认为无序集能做到这一点

鉴于上述情况,我建议您使用排序向量作为"集合"。使用平分,您可以在O(logn(时间内搜索存储,并在列表中迭代是微不足道的。向量还有一个额外的优点,即内存是连续的,因此您不太可能遇到缓存未命中的情况。

我强烈建议您不要在这种情况下使用。set是二叉树,unordered_set是哈希表,因此它们占用大量内存,迭代速度慢,引用的局部性差。如果您必须频繁插入/删除/查找数据,setunordered_set是不错的选择,但现在您只需要读取、存储、排序数据一次,只使用数据多次。

在这种情况下,排序向量可能是一个很好的选择。vector是动态数组,因此开销较低。

直接查看代码。

std::vector<int> data;
int input;
for (int i = 0; i < 10; i++)
{
    std::cin >> input;
    data.push_back(input); // store data
}
std::sort(data.begin(), data.end()); // sort data

仅此而已。您的所有数据都已准备就绪。

如果您需要删除像set这样的重复项,只需在排序后使用unique-erase即可。

data.erase(
    std::unique(data.begin(), data.end()),
    data.end()
    );

请注意,您应该使用lower_boundupper_boundequal_range,而不是findfind_if来使用排序数据的好处。