【问题标题】:set vs unordered_set for fastest iterationset vs unordered_set 最快迭代
【发布时间】:2014-08-21 19:23:52
【问题描述】:

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

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

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

约束 1 表明我必须使用 set 或 unordered_set,因为数据不是键值对的形式。

现在 set 插入比 unordered_set 插入更昂贵,但数据结构仅在我的程序开始时填充一次。

我相信决定因素将是我能够以多快的速度迭代数据结构的所有元素。为此,我不确定 set 或 unordered_set 是否会更快。我相信标准没有提到这个事实,因为对于任何一种数据结构,这个操作都是 O(n)。但我想知道哪个数据结构 iterator.next() 会更快。

【问题讨论】:

  • 所以,测量一下看看。
  • 矢量怎么样?
  • 您可以为不同的目的使用不同的结构。例如,构建一个set,然后在完成后将所有内容复制/移动到vector 中以进行快速迭代。因此,问题是:一旦冻结,除了迭代之外,您还需要什么else? (如快速查找)
  • @AviralGoel:你认为这个系列神奇地不需要支付那个成本吗?
  • @JoachimPileborg: std::lower_bound :-S

标签: c++ c++11 stl set unordered-set


【解决方案1】:

有几种方法。

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

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

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

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

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

【讨论】:

  • 查找比散列容器更快?我对此表示怀疑。
  • @Deduplicator 它们的意思是比std::set 更快,后者在连续内存上执行O(log N) 指针追逐而不是O(log N) 二进制搜索
  • 好吧,比你应该添加一个澄清的评论。
  • 似乎有点矫枉过正,当一个简单的 std::vector 就足够了。
  • @MatthiasB 可能是,但它是std::set 的“插入式”替代品,因此您不必更改呼叫代码。
【解决方案2】:

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

【讨论】:

  • unordered_setset 相比,对于“过滤”来说是一个糟糕的选择。测试单个元素是否已经存在是线性时间。
  • @UmNyobe 平均而言,unordered_setset 的对数相比具有恒定的查找复杂度
【解决方案3】:

如果数据结构的构建没有考虑到性能问题(或至少只是很少考虑),请考虑将您的数据保存到 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);

【讨论】:

    【解决方案4】:

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

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

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

    鉴于上述情况,我建议您为“集合”使用排序向量。使用二分法,您可以在 O(log n) 时间内搜索商店,并且迭代列表是微不足道的。向量具有内存连续的额外优势,因此您不太可能遇到缓存未命中。

    【讨论】:

      【解决方案5】:

      我强烈建议您不要在这种情况下使用。 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 来利用排序数据的好处。

      【讨论】:

        猜你喜欢
        • 2014-12-18
        • 1970-01-01
        • 1970-01-01
        • 2017-11-27
        • 2013-09-13
        • 2020-11-23
        • 2021-02-09
        • 1970-01-01
        相关资源
        最近更新 更多