【问题标题】:How to make a fast dictionary that contains another dictionary?如何制作包含另一本词典的快速词典?
【发布时间】:2013-07-21 07:44:14
【问题描述】:

我有一个map<size_t, set<size_t>>,为了获得更好的性能,我实际上将其表示为按字典顺序排序的vector<pair<size_t, vector<size_t>>>

我需要的是一个具有 快速 插入时间的set<T>(删除无关紧要),其中T 是上面的数据类型,以便我可以检查重复项(我的程序运行直到不再生成唯一的T。)。

到目前为止,从 set 切换到 unordered_set 已经证明是非常有益的(它使我的程序运行速度快了 25%),但即使是现在,插入 T 似乎仍然是主要的方法之一瓶颈。

给定T 中整数的最大数量约为 1000,每个整数也 T) .

我已经尝试过的:

  • 使用unsigned short。它实际上会稍微降低性能。

  • 使用 Google 的btree::btree_map
    它实际上要慢得多,因为我必须解决迭代器失效问题。
    (我必须复制密钥,我认为这就是它变慢的原因。它至少慢了一倍。)

  • 使用不同的哈希函数。只要我使用合理的东西,我还没有发现任何可衡量的差异,所以这似乎无法改进。

我没有尝试过:

  • 存储“指纹”/哈希而不是实际集合。
    这听起来像是一个完美的解决方案,除了指纹识别功能需要快速,而且我需要非常确信不会发生碰撞,否则它们会搞砸启动我的程序。
    (这是一个需要精确结果的确定性程序;碰撞使其无用。)

  • 以其他紧凑、CPU 友好的方式存储数据。
    我不确定这会有多大好处,因为它可能涉及复制数据,到目前为止我获得的大部分性能是通过(巧妙地)避免在许多情况下复制数据。

如果有的话,我还能做些什么来提高速度?

【问题讨论】:

  • 你使用 reserve() 吗?如果您有大量数据,则 hash_map 需要巨大的动态数组。那是瓶颈。
  • @thomas:是的,我愿意。 :) 我对几乎所有东西(包括哈希表)都使用reserve,并避免使用移动/交换来复制容器。
  • 请问T到底是什么类型的?
  • @larsmans: "... 其中T是上面的数据类型", vector<pair<size_t, vector<size_t>>>.
  • @Mehrdad:哪种类型? map<size_t, set<size_t>>? pair<size_t, vector<size_t>>?

标签: c++ performance data-structures set hashtable


【解决方案1】:

我的印象是您在这里有 3 个不同的问题:

  • T 本身需要相对紧凑且易于移动
  • 您需要快速检查T 是否可能与现有的重复
  • 您最终需要将新的T 快速插入到必须检查重复项的任何数据结构中

关于T 本身,它还没有达到应有的紧凑程度。您可能可以使用单个 std::vector<size_t> 来表示它:

  • N 对
  • N 个索引
  • 每个 I 元素的 N 个“ID”

所有可以线性化的:

[N, I(0), ..., I(N-1),
    R(0) = Size(Id(0)), Id(0, 0), ... , Id(0, R(0)-1),
    R(1) = ... ]

这样你就有了一块内存。

注意:根据访问模式,您可能需要对其进行调整,特别是如果您需要随机访问任何 ID。


关于重复的可能性,hash-map 似乎确实非常合适。您将需要一个良好的哈希函数,但是对于单个数组 size_t(或 unsigned short,如果可以的话,它会更小),您可以选择 MurmurHash 或 CityHash 或 SipHash。他们都在飞速发展,尽最大努力产生高质量的哈希(不是加密的,重点是速度)。

现在,问题是什么时候检查重复项时会很慢。

如果您因为哈希图太大而花费太多时间检查不存在的重复项,您可能需要在它前面投资一个布隆过滤器。

否则,请检查您的哈希函数以确保它非常快且冲突率低,并检查您的哈希映射实现以确保它只计算一次哈希。


关于插入速度。通常,哈希映射,特别是如果平衡良好且预先确定大小,应该是最快的插入之一。确保将数据移入其中,不要复制;如果您无法移动,可能值得使用shared_ptr 来限制复制成本。

【讨论】:

  • +1 以获得惊人的答案,我仍在消化它。 :) 关于使T 紧凑,这是可能的,但需要我复制数据;不过,由于插入速度更快,它可能会有所回报,我会研究一下。还要感谢您提到哈希名称,这非常有用。 :)
  • Murmurhash 以及连续打包数据帮助我获得了大约 8% 的改进。
  • @Mehrdad:感谢您的反馈!不幸的是它看起来很小,所以散列可能不是这里的主要问题:x
  • 它仍然很棒 :) 我会接受这个答案,因为它为我指明了很好的方向!
【解决方案2】:

不要害怕冲突,使用加密哈希。但是选择一个快速的。 256 位冲突的可能性远低于硬件错误。 Sun 使用它对 ZFS 中的块进行重复数据删除。 ZFS 使用 SHA256。可能您可以使用不太安全的哈希。如果需要 1000000 美元才能找到一个冲突哈希是不安全的,但一个冲突似乎不会降低您的性能。许多碰撞将花费许多$ 1000000。您可以使用(无序的)multimap<SHA, T> 之类的东西来处理冲突。顺便说一句,任何哈希表都会发生冲突(或占用太多内存),因此有序映射(gcc 中的 rbtree)或 btree_map 具有更好的时间保证。哈希表也可以通过哈希冲突来处理。可能一种秘盐可以解决这个问题。这是由于表大小远小于可能的哈希数。

【讨论】:

  • SHA256 约为 100Mb/s link。似乎所有加密哈希都很慢。
【解决方案3】:

您还可以: 1)使用短整数 2)将您的数组重新解释为 uint64_t 之类的数组以进行快速比较(+一些尾随元素),甚至将其重新解释为 128 位值(或 256 位,取决于您的 CPU)的数组并通过 SSE 进行比较。这应该会将您的性能推到内存速度限制。 根据我的经验,SSE 只能通过对齐的内存访问快速工作。 uint64_t 比较可能也需要对齐速度,因此您必须手动分配内存并进行正确对齐(分配更多并跳过第一个字节)。 tcmalloc 是 16 字节对齐的,uint64_t-ready。奇怪的是你必须在 btree 中复制密钥,你可以使用 shared_ptr 来避免它。使用快速比较和慢速哈希 btree 或 std::map 可能会比哈希表更快。我猜任何哈希都比内存慢。您还可以通过 SSE 计算哈希,并且可能会找到一个库。


PS 如果您还没有,我强烈建议您使用分析器。请告知您的程序插入、比较插入和计算哈希所花费的时间百分比。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-25
    • 2021-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-28
    相关资源
    最近更新 更多