【问题标题】:Fast container for setting bits in a sparse domain, and iterating (C++)?用于在稀疏域中设置位和迭代(C++)的快速容器?
【发布时间】:2010-09-23 14:27:03
【问题描述】:

我需要一个只有两个操作的快速容器。从一个非常稀疏的域插入键(所有 32 位整数,并且在给定时间设置大约 100 个),并迭代插入的键。它应该处理 很多 插入相同的条目(例如,500k,但只有 100 个不同的条目)。

目前,我正在使用 std::set(仅插入和迭代接口),这很不错,但仍然不够快。 std::unordered_set 慢了一倍,与 Google Hash Maps 相同。我想知道针对这种情况优化了哪些数据结构?

【问题讨论】:

  • 不能说这有多大影响,但如果您使用的是 MSVC 2005 或更高版本,则默认定义 _SECURE_SCL,这会强制对大多数 STL 进行边界检查和其他安全检查。试试“#define _SECURE_SCL 0”,看看是否能提高性能。

标签: c++ containers


【解决方案1】:

根据输入的分布情况,您可能能够在不改变结构的情况下获得一些改进。

如果您倾向于多次运行单个值,那么您可能可以通过记录您插入的最后一个值来加快插入速度,并且如果匹配则不要打扰插入。每个输入都需要进行额外的比较,但在第一个之后的运行中节省了对每个元素的查找。因此,无论您使用什么数据结构,它都可以改进,具体取决于重复频率以及比较与插入的相对成本。

如果您没有运行,但您往往会发现值分布不均,那么展开树可以降低访问最常用元素的成本。它通过使用靠近顶部的频繁元素创建一个故意不平衡的树来工作,就像霍夫曼代码一样。

【讨论】:

    【解决方案2】:

    我不确定我是否理解“许多插入相同条目的内容”。你的意思是只有 100 个值是成员,但是 500k 大部分重复的操作会插入这 100 个值之一?

    如果是这样,那么我猜最快的容器是在这 100 个值上生成一个无冲突的散列,然后根据最快的结果维护一个标志数组(或向量)(int 或 bit)在你的架构上)。

    我将生成哈希作为练习留给读者,因为我知道它是作为一种技术存在的,但我自己从未研究过它。关键是在尽可能小的范围内获得快速散列,这样对于 100 个值中的每个 n,m,hash(n) != hash(m)。

    所以插入看起来像array[hash(value)] = 1;,删除看起来像array[hash(value)] = 0;(虽然你不需要那个),并且枚举你在数组上运行,并且对于索引n处的每个设置值,inverse_hash(n)是在您的收藏中。对于较小的范围,您可以轻松地维护一个查找表来执行反向哈希,或者您可以运行超过 100 个潜在的值,依次检查每个值,而不是扫描整个数组以查找设置标志。

    对不起,如果我误解了情况,这对你没用。老实说,它并没有比普通的哈希表快多少,因为实际上对于 100 个值,您可以轻松地调整表的大小,从而减少或没有冲突,而不会使用太多内存来破坏缓存。

    【讨论】:

    • 它可能是(可能是,在猜测)我们不知道在任何给定点上可能有哪些 100 左右的值,所以我们真的没有选择为他们生成一个完美的哈希函数。您仍然需要一个非分桶哈希表来执行此操作。
    • 你是对的,这 100 个值是事先不知道的,而且每次运行它们都不同。唯一不变的是独特元素的数量,它始终在 100-1000 左右。
    • 妈的,缓存最后一个ID就够了。您的猜测是正确的,这些值往往会重复很长一段时间,并且通过缓存最后一个数字,我获得了 100% 的更好性能。谢谢!
    • 万岁猜,第二次幸运:-)
    【解决方案3】:

    对于预期如此小的在用集,非分桶哈希表可能没问题。如果你可以忍受偶尔的扩展操作,如果它的容量超过 70%,则以 2 的幂增长。 Cuckoo hashing 一直是 discussed on Stackoverflow before 并且对于这么小的集合来说也可能是一个好方法。如果你真的需要优化速度,你可以在汇编器中实现散列函数和查找——在线性数据结构上这将非常简单,因此汇编器实现的编码和维护工作应该不会太难维护。

    【讨论】:

      【解决方案4】:

      您可能需要考虑在每个级别使用以 10 为底的哈希函数而不是二进制哈希函数来实现 HashTree。您可以将其设为非分桶,在这种情况下您的性能将是确定性的 (log10) 或根据您的预期分布调整您的存储桶大小,以便您只有几个键/存储桶。

      【讨论】:

        【解决方案5】:

        随机数据结构可能非常适合您的工作。看看skip list——尽管我不知道它的任何 C++ 实现。我打算向 Boost 提交一个,但一直没有时间去做。

        【讨论】:

          【解决方案6】:

          可能是一个以b-tree(而不是二叉树)作为内部数据结构的集合。我在 codeproject 上找到了 this 文章,它实现了这一点。

          【讨论】:

            【解决方案7】:

            请注意,虽然插入哈希表很快,但迭代它并不是特别快,因为您需要迭代整个数组。

            哪个操作对你来说很慢?你做更多的插入还是更多的迭代?

            【讨论】:

            • 问题是插入经常碰到相同的值,因此需要 O(1) 测试是否包含一个值(阅读:哈希图)。出于某种原因,MSVC std::set 仍然比 std::tr1::unordered_map 快,尽管 std::set 的查找是 O(log n)。
            【解决方案8】:

            你有多少内存? 32 位“仅”占用 4GB/8 字节,即 512MB,对于高端服务器来说并不多。这将使您的插入 O(1)。但这可能会使迭代变慢。尽管跳过所有只有零的单词会优化大多数迭代。如果您的 100 个数字在相对较小的范围内,您可以通过保持最小值和最大值来进一步优化。

            我知道这只是蛮力,但有时蛮力就足够了。

            【讨论】:

            • 如果只对插入感兴趣而不是删除,迭代问题可以通过维护一个额外的未排序向量/所有项目的链表来解决,插入 O(1),迭代 O(n) .
            【解决方案9】:

            由于没有人明确提到它,您是否考虑过内存局部性?具有导致页面错误的插入算法的真正出色的数据结构对您没有好处。实际上,带有仅导致缓存未命中的插入的数据结构可能对性能非常不利。

            当插入碰撞太慢时,您是否确保将一组天真的无序元素打包在一个固定数组中,并通过简单的交换到前面?这是一个简单的实验,可能表明您存在内存局部性问题而不是算法问题。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2018-08-23
              • 1970-01-01
              • 2021-07-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-12-05
              相关资源
              最近更新 更多