不同数据结构的速度/内存使用估计

时间:2011-07-13 11:42:05

标签: c++ data-structures hashtable binary-tree radix-tree

我正在尝试决定使用哪种数据结构。

假设我有1000万个键,其中包含指向包含某些数据的唯一对象的指针。

密钥是UUID将它们视为16字节二进制数组。 UUID是使用高质量的随机数生成器生成的。

我一直在考虑以下内容,但想知道速度和内存消耗方面的优缺点是什么。一些公平的估计,64位平台上的最佳/最差/平均情况会很好。

我需要能够插入几乎无限的项目。

二叉树 哈希表 基数树(基于比特或2比特多路)

我需要的操作是:插入,删除,搜索

我喜欢基数树的想法但它被证明是最难实现的,我没有找到合适的实现,我可以将其整合到商业产品中。

4 个答案:

答案 0 :(得分:5)

  • 您不关心订购
  • 您的密钥已经是随机的
  • 1000万件物品

简短回答

哈希表可能是您的最佳选择。

<强>速度

如果散列是常量,则散列表(std::unordered_map)将 O (1)。在你的情况下, O (1)成立,因为你甚至不需要哈希 - 只需使用随机UUID的低32位应该足够好。查找的成本将类似于一个或两个指针间接。

二叉树(std::map)将 O (log 2 n ),因此对于1000万件物品你是'将进行24次比较和24次潜在的缓存未命中。即使对于 n = 4,000,它也会使用12次比较,因此很快就会变得比哈希表差得多。

基数树将 O k ),因此您将最多具有 k 比较和 k < / em>潜在的缓存未命中。在极不可能的最佳情况下,基数树将与哈希表一样快。更糟糕的是(假设 k =有点合理的16,对于256路树),它的性能优于二叉树,但远比哈希表差。

因此,如果速度是最高优先级,请使用哈希表。

<强>开销

如果已满,典型的哈希表每个项目将有大约1-3个开销指针。如果不满,你可能每个空槽浪费1个空格指针。你应该能够保持它几乎满,同时仍然比二叉树快,因为你有一个非常随机的键,但为了最大可能的速度,你当然希望给它足够的余量。对于32位机器上的1000万个项目,预计全表的开销为38-114MiB。对于半满表,预计为76-153MiB。

红黑树,最常见的std::map实现,每个项目有3个指针+ 1个bool。一些实现利用指针对齐来将bool与其中一个指针合并。根据实现以及哈希表的完整程度,红黑树的开销可能略低。预计114-153MiB。

基数树每个项目有1个指针,每个空槽有1个指针。不幸的是,我认为这么大的随机密钥会让你在树的边缘有很多空插槽,所以它可能会比上面的任何一个使用更多的内存。减少 k 可以降低此开销,但同样会降低性能。

如果最小开销很重要,请使用哈希表或二叉树。如果是优先级,请使用完整的哈希表。

请注意,std::unordered_map不允许您控制何时调整大小,因此获得一个完整将很困难。 Boost Intrusive有一个非常好的unordered_map实现,可以让您直接控制它和许多其他内容。

答案 1 :(得分:1)

我会首先尝试std::mapstd::unordered_map

多年来,他们有很多聪明人开发和改进它们。

有没有理由不能使用std::mapstd::unordered_map

答案 2 :(得分:1)

我只是做了一个快速的计算,我认为你可以使用标准树。 1000万个钥匙是一个合理的数字。使用平衡树,只需要检查23个节点的深度。使用基数树,您实际上需要128个密钥长度来检查。

您的密钥也可以非常便宜地进行表示和比较。使用两个64位值的元组(boost或0x)来获得相同的128位密钥。元组排序足以在地图中使用。因此比较便宜的密钥复制。按原样比较整数可能比对基数深度搜索进行掩蔽和基于比特的比较更便宜。

因此,在这种情况下,地图很可能正常工作。

*我在这里避免使用unordered_map,因为UUID往往是结构化数据。这意味着标准的散列过程(对于哈希映射)很容易在性能上很差。 *

更新:

由于您使用的是随机UUID,因此散列可能很好 - 尽管这种大型散列表具有显着的内存开销以保持高效。

此外,给定完全随机的UUID,基数可能最终具有与树相同的平衡(因为密钥分布完全均匀)。因此,您可能无法保存甚至步骤,仍然会产生位操作的开销。但是有很多方法可以专门化和优化基数树,如果它可以更快或者总是更慢,很难肯定。

答案 3 :(得分:0)

IMO基数树并不难实现。但是,一个简单的哈希表就足够了。只需分配2 ^ 16个对象列表的数组,并使用UUID的前2个字节来索引列表插入对象的位置。然后你可以用大约160个项目搜索列表。

或者,分配20M指针的数组。要存储对象,只需在0-20M范围内创建UUID的哈希值,找到第一个空闲(NULL)指针并将其存储在那里。搜索意味着从散列值走到第一个NULL值。删除也很简单....尝试阅读http://en.wikipedia.org/wiki/Hash_function