从巨大的列表中删除大量字符串

时间:2013-02-11 15:07:59

标签: c++ performance list

我有一个大字符串列表存储在一个巨大的内存块中(通常有100k +甚至1M +)。这些实际上是哈希值,因此字符串的字母表限制为A-F0-9,每个字符串的长度恰好为32个字节(因此它存储为“压缩”)。我将从现在开始将此列表称为主列表

我希望能够从主列表删除项目。这通常是以批量方式完成的,所以我得到一个大的列表(通常大约100到10k)哈希,我需要在这个列表中找到并删除它们。在此操作结束时,大内存块中不能有任何空块,因此我需要考虑到这一点。不保证所有项目都在主列表中,但不会有多次。无法重新定位,主要块将始终保持相同的大小。

通过主列表迭代并检查是否已删除给定哈希的天真方法当然有效,但有点慢。此外,小内存块的移动有点过多,因为每次将哈希标记为删除时,我都会使用主列表的最后一个元素重写它,从而满足无空块的条件。这当然会创建成千上万的小memcpy ,这反过来会减慢因为我得到大量的缓存未命中。

有更好的方法吗?

一些重要的注释:

  • 主列表没有排序,我不能浪费时间对它进行排序,这个 是整个项目施加的限制,并重写它 list总是排序不是一个选项(甚至可能不是 可能的)
  • 内存不是真正的问题,但使用的越少越好
  • 我可以使用STL,但不能提升

2 个答案:

答案 0 :(得分:2)

好的,如果我绝对不得不优化地狱,那么这就是我要做的事情。 我假设订单无关紧要,因为您(IIUC)通过将项目与最后一项交换来删除项目似乎就是这种情况。

  • 存储128位整数(但是你代表它们,或者你的编译器本身支持它们,或者你使用一个32/64位整数的小数组)而不是32-char字符串。请参阅我对该问题的评论。
  • 滚动我自己的128位整数哈希集。请注意,如果您愿意稍微考虑一下,做出一些假设,那么你可以在这里优化很多。一些说明:
    • 您只需要存储哈希值(用于冲突解决),以及一些或两个元数据来识别已删除/未使用的插槽。如果您不确定如何保证正确性,请查看现有哈希表的作用。如果你在构建哈希集之后只删除(不添加),我认为它更简单。虽然我认为如果你的值不是一个有效的哈希来表示空槽,你甚至可以没有那个元数据,但这种方式删除更容易(只需翻转一下,而不是覆盖128位)。
    • 您不需要哈希函数,因为您的输入已经是整数。你只需要做每个哈希表所做的事情:采用模2 ^ n的哈希来导出一个并不是很大的索引。选择n使得负载因子(使用的表条目的百分比)是合理的(< 2/3似乎是标准的)。选择功率使模运算更便宜(通过二进制AND屏蔽掉位),并允许您在较低的32位或64位(忽略其余位)执行此操作。
    • 选择碰撞解决策略很难。作为第一次尝试,我可能会使用open addressing进行线性探测。 可能工作得很糟糕,但如果你的输入哈希是好的,这似乎不太可能。还有一种探测方案会考虑CPython's dict使用的最初切断的越来越多的比特。

现在,与使用现成的解决方案相比,这会带来更多的工作和维护负担。我不会建议它,除非它真的像你的描述中那样对性能至关重要。 如果C ++ 11是一个选项,并且编译器的unordered_set是好的,也许你应该只使用它并节省大部分麻烦(但要注意这可能会增加内存需求)。您仍然需要专门化std::hashstd::equal_tooperator==。替代为Hash提供您自己的KeyEqualunordered_set,但这可能不会带来任何好处。

答案 1 :(得分:1)

两件事可能有所帮助。首先,至少对项目列表进行排序 即将被删除;这样,您就可以使用二进制搜索 (std::lower_bounds)就可以了。第二,保持两个指针: 来源和目的地。如果源指向某事 不在要删除的列表中,将其复制到目标,然后 推进两者。如果源指向要删除的内容, 只需前进源指针,无需复制。应该 永远不会成为不止一次复制条目的理由。