我有一个map<size_t, set<size_t>>
,为了更好的表现,我实际上代表的是按字典顺序排序的vector<pair<size_t, vector<size_t>>>
。
我需要的是set<T>
快插入时间(删除无关紧要),其中T
是上面的数据类型,以便我可以检查重复(我的程序运行,直到不再生成唯一的T
。)。
到目前为止,从set
切换到unordered_set
已经证明非常有用(它使我的程序运行速度提高了25%),但即使是现在,仍然插入T
似乎是主要的瓶颈之一。
给定T
中的最大整数数约为〜1000,每个整数也<= ~1000,因此数字非常小(但有数千个T
正在生成)。
我已经尝试过:
使用unsigned short
。它实际上会略微降低性能。
使用Google的btree::btree_map
。
它实际上要慢得多,因为我必须解决迭代器失效问题
(我必须复制键,我认为这就是为什么它变慢了。它至少慢了两倍。)
使用不同的哈希函数。只要我使用合理的东西,我就没有发现任何可衡量的差异,所以看起来似乎无法改善。
我不尝试了什么:
存储“指纹”/哈希值而不是实际集合
这听起来像是完美的解决方案,除了指纹识别功能需要快,我需要非常确信不会发生碰撞,或者他们会搞砸我的计划。
(这是一个需要精确结果的确定性程序;碰撞使它无用。)
以其他紧凑,CPU友好的方式存储数据 我不确定这会有多大益处,因为它可能涉及复制数据,到目前为止我获得的大部分性能都是(巧妙地)避免在许多情况下复制数据。
答案 0 :(得分:1)
我的印象是你在这里有3个不同的问题:
T
本身相对紧凑且易于移动T
是否可能与现有的T
关于T
本身,它还没有尽可能紧凑。您可以使用单个std::vector<size_t>
来表示它:
所有这些都可以线性化:
[N, I(0), ..., I(N-1),
R(0) = Size(Id(0)), Id(0, 0), ... , Id(0, R(0)-1),
R(1) = ... ]
这样你只有一块内存。
注意:根据访问模式,您可能需要调整它,特别是如果您需要随机访问任何ID。
关于重复的可能性,哈希映射似乎确实非常合适。你需要一个好的哈希函数,但是如果你可以使用size_t
(或unsigned short
的单个数组,那么你可以选择MurmurHash或CityHash或SipHash。它们都非常快速,并且可以产生高质量的哈希(不是加密的哈希,重点是速度)。
现在,问题是当检查重复项时是否很慢。
如果您花费太多时间检查不存在的重复项,因为哈希映射太大,您可能希望在它前面投资Bloom过滤器。
否则,检查你的哈希函数以确保它真的很快并且具有低冲突率和你的哈希映射实现,以确保它只计算一次哈希值。
关于插入速度。通常情况下,哈希映射,特别是如果平衡和预先调整大小,应该具有最快插入之一。确保将数据移入其中并且不要复制它;如果你不能移动,可能值得使用shared_ptr
来限制复制成本。
答案 1 :(得分:0)
不要害怕碰撞,使用加密哈希。但选择一个快速的。 256位冲突比硬件错误更不可能。 Sun使用它来重复删除ZFS中的块。 ZFS使用SHA256。可能你可以使用安全性较低的哈希值。如果找到一个碰撞哈希需要100万美元,那么哈希是不安全的,但是一次碰撞似乎不会降低你的性能。许多碰撞将花费100多万美元。您可以使用(无序)multimap<SHA, T>
之类的东西来处理冲突。顺便说一句,ANY哈希表遭受冲突(或占用太多内存),因此有序映射(gcc中的rbtree)或btree_map具有更好的保证时间。哈希表也可以通过哈希冲突进行DOS操作。可能是秘密盐可以解决这个问题。这是由于表格大小远小于可能的哈希值。
答案 2 :(得分:0)
您还可以: 1)使用short int 2)将数组重新解释为类似uint64_t的数组以进行快速比较(+一些尾随元素),或者甚至将其重新解释为128位值(或256位,取决于您的CPU)的数组,并通过SSE进行比较。这应该会将您的性能提升到内存速度限制。 根据我的经验,SSE仅使用对齐的内存访问快速工作。 uint64_t比较可能也需要对齐速度,所以你必须手动分配内存并进行适当的对齐(分配更多并跳过第一个字节)。 tcmalloc是16字节对齐的,uint64_t-ready。很奇怪你必须在btree中复制密钥,你可以使用shared_ptr来避免它。通过快速比较和慢速哈希btree或std :: map可能会比哈希表更快。我猜任何哈希都比内存慢。您还可以通过SSE计算哈希值,并可能找到一个可以执行此操作的库。
PS我强烈建议你使用探查器,如果你还没有。请告诉您程序花费的时间百分比,插入和计算哈希值进行比较。