【问题标题】:Choosing between set<int> vs. vector<bool> vs. vector<boolean_t> to use as a bitmap (bitset / bit array)在 set<int> 与 vector<bool> 与 vector<boolean_t> 之间进行选择以用作位图(位集/位数组)
【发布时间】:2010-11-11 15:10:53
【问题描述】:

给定一系列索引(标识符),我想将每个索引映射到一个布尔值,即:

// interface pseudocode
interface bitmap {
  bool identifier_is_set(unsigned int id_idx) const;
  void set_identifier(unsigned int id_idx, bool val) const;
};

这样我就可以设置和查询每个 ID(索引)是否已设置,您希望使用什么来实现它?

我认为这称为位数组或位图或位集,如果我错了,请纠正我。

假设最大标识符是预先确定的,并且不大于 1e6 (1m),可能要小得多 (10k - 100k)。 (这意味着 sizeof(int)*maximum_id_idx 使用的大小很容易适合进入记忆。)

目前我看到的可能解决方案:

  • std::set&lt;size_t&gt; - 根据需要向该集合添加或删除标识符。只要我们有一个稀疏位图,这将允许任意大的标识符。
  • std::vector&lt;bool&gt; - 大小为适当的最大值,为每个 id_idx 存储 true 或 false。
  • std::vector&lt;char&gt; - 同样的事情,但没有遇到奇怪的std::vector&lt;bool&gt; 问题。使用的内存比 vector&lt;int&gt; 少。
  • std::vector&lt;int&gt; - 使用 int 作为布尔标志来拥有使用机器自然字长的容器。 (不知道这是否会有所作为。)

请回答您更喜欢哪种容器类型以及原因,考虑到上面提到的最大 id 限制,特别是考虑到 查询位图的性能方面(插入性能无关紧要)。

注意:vectorset 的接口用法无关紧要,因为无论如何它都会隐藏在它的包装类后面。

编辑:添加关于 std::bitset 的讨论:std::bitset 会将整个数组大小合并到对象中,即 sizeof(std::bitset) 的大小约为 1 /8 兆字节,这构成了一个巨大的单个对象,并且构成了您无法再放入堆栈的东西(可能相关也可能不相关)。

【问题讨论】:

  • 当性能很重要并且您最初无法做出“明显”的选择时,您必须运行测试来比较等效行为。

标签: c++ performance


【解决方案1】:

在不知道运行此代码的平台和访问模式的情况下,很难说vector&lt;bool&gt; 是否会比vector&lt;char&gt;(或vector&lt;int&gt;)甚至set&lt;int&gt;unordered_set&lt;int&gt; 更快。

例如,如果您有一个非常稀疏的数组,则对仅包含索引集的vector&lt;int&gt; 进行线性搜索可能是最佳答案。 (See Mike Abrash's article on optimizing Pixomatic for x86.)

另一方面,您的数组可能有些稀疏。稍微稀疏,我的意思是集合元素的数量远大于 L1 或 L2。在这种情况下,更多底层细节以及您的实际访问模式开始发挥作用。

例如,在某些平台上,可变位移非常昂贵。因此,如果您要查询一组随机标识符,则执行此操作的频率越高,vector&lt;char&gt;vector&lt;int&gt; 就越会比 bitset&lt;...&gt;vector&lt;bool&gt; 更好。 (后两者使用位移来查找位。)另一方面,如果您按顺序迭代稀疏位向量并且只希望设置位,则可以优化该迭代以消除变量移位的开销。

此时,您可能还想知道稀疏标识符的实际分布情况。如果它们聚集在一起,您需要知道最佳内存读取大小和一次读取一个字符之间的权衡。这将决定更频繁地访问缓存是否会抵消读取非原生大小的数据。

如果标识符是分散的,您可能会通过使用哈希集 (unordered_set&lt;int&gt;) 而不是位向量来获得重大胜利。不过,这取决于负载。

【讨论】:

    【解决方案2】:

    【讨论】:

    • 谢谢。我不需要 dynamic_bitset 定义的任何集合操作,所以看起来这个类只会在不需要的地方增加开销。
    • dynamic_bitset 就像 std::bitset 但可以调整大小。如果您不使用它,额外的功能不会花费。
    • @Martin:我同意@CashCow。这是模板的一个优点;如果您不实例化它们,将不会生成这些函数的代码。
    【解决方案3】:

    假设最大标识符是预先确定的并且不大于1e6(1m)

    如果您有硬限制,请使用std::bitset

    std::bitset<1000000> bits;
    bits.set(1000);
    

    【讨论】:

      【解决方案4】:

      如果你所说的性能是指查找速度最快的那个,那么 std::bitset 可能足够快,因为它的查找是恒定时间的。将所有位设置为零有初始开销。 vector 可能会更快,并且设置位的开销会更大,因为在 32 位系统中它们的数量是它们的 32 倍。

      vector 在它的实现中类似于 bitset,如果你需要的话,它的优点是可以调整大小,尽管一般来说我会避免使用向量,如果我需要调整大小,可以使用 boost 的 dynamic_bitset。

      std::set 在查找和插入/删除中将是 O(log N),尽管它在内存使用方面是最具可扩展性的,如果集合不是特别满则占用更少。 std::set 没有范围限制。

      如果您的数据比较稀疏,也可以选择某种形式的散列,通常为 O(1) 设置和查找,尽管可能存在一些冲突处理开销。

      【讨论】:

      • std::bitset 不合适,因为最大大小可能会有所不同。大约运行时 10k 和 1m
      • @Martin:如果您知道最大大小是多少,那么 bitset 仍然可以工作,并且会比任何动态选项都快。
      • 史蒂夫:请参阅stackoverflow.com/q/4156538/321013,因为我不明白具有任意大尺寸的std::bitset 在查找时如何比std::vector&lt;bool&gt; 更快
      • 对于可调整大小的位集,您可以使用 boost::dynamic_bitset(在第二段末尾附近提到)。
      【解决方案5】:

      最快的似乎是使用位掩码。您应该构造一个std::vector&lt;int&gt;,并使其大小足够(N 除以 sizeof(int)*8,向上取整)。

      对于大型数据集,这似乎比std::vector&lt;bool&gt;(或类似的)更快。因为您实际上使用的内存要少得多,因此缓存利用率更高

      【讨论】:

      • std::vector&lt;int&gt; 用作位掩码与应该这样做的std::vector&lt;bool&gt; 有何不同
      【解决方案6】:

      你总是可以有一个std::vector&lt;std::bitset&lt;sizeof(size_t)&gt; &gt;,然后你的查找是简单的计算(虽然模运算相对较慢),但你的优势是能够增长......我会冒险空间明智,上面可能也是最优化的......

      【讨论】:

        猜你喜欢
        • 2017-09-01
        • 2011-05-02
        • 1970-01-01
        • 2011-11-15
        • 2011-08-14
        • 1970-01-01
        • 2020-05-10
        相关资源
        最近更新 更多