C ++将预先保留的哈希图std :: unordered_map与整数键和连续数据数组std :: vector进行比较

问题描述

假设使用具有int键类型的哈希映射结构:

std::unordered_map<int,data_type> um;

此外,当已知N个元素的总数(或最大)时,可以预先构造哈希表。

um.reserve(N); // This will chainly call rehash() function...

据我所知,这里整数本身可以用作哈希表的 identity(hash)函数

同时,对于连续数据集(即std::vector或简单数组),可以通过从地址的位置进行移位随机访问最前面的数据。

两个容器都使用int作为访问密钥,如下所示:

um[1] = data_type(1); //std::unordered_map<int,data_type>
v[1] = data_type(1); //std::vector<data_type>

然后,构造的哈希表和std::vector在内存使用或搜索机制/性能或其他任何方面是否有区别?

让问题更明显。

如果我知道肯定使用了3个键059987,但是可能会或可能不会使用键19986

如果我知道集合中的任何键都不会大于10000,则使用大小为std::vector的{​​{1}}将保证O(1)的时间复杂度,可访问随机数据,但可以存储将被浪费。

在这种情况下,10000是否为该问题提供了更好的解决方案? *我的意思是,这样一种解决方案可以节省尽可能多的内存,同时将时间复杂度保持在同一水平。

解决方法

一切都不同。

unordered_map的概念为buckets-

存储桶是容器内部哈希表中的插槽,根据其键的哈希值将元素分配给该插槽。存储桶的编号从0到(bucket_count-1)。

unordered_map计算指向存储桶的键的哈希值。所需值在该存储桶中。现在请注意,多个键可以指向一个存储桶。在您的情况下,甚至可能发生um[0]um[5]um[9987]都位于同一存储桶中的情况!存储桶中的搜索时间是线性的。

在这种情况下,std :: unordered_map是否可以为该问题提供更好的解决方案?

如果数据稀疏,请使用unordered_map,但要保留适当的存储空间(或完全不保留存储空间,并使用默认分配策略)。如果执行myMap.reserve(MAX_ELEMENTS)是没有意义的,因为那样会再次导致内存浪费。

否则,请使用向量。您将获得有保证的O(1)查找。由于其线性,因此超级缓存友好。而在unordered_map上,您可能会得到最坏情况的查询O(N)

,

此外,当已知元素的总数(或最大)N时,可以预先构造哈希表。

um.reserve(N); //这将链接调用rehash()函数...

据我所知,这里整数本身可以用作哈希表的标识(哈希)函数。

这是正确的,并且在两种截然不同的情况下是合理的:1)当值非常连续且可能缺少一些值时,或者2)当值非常随机时。在许多其他情况下,如果您不提供有意义的哈希函数,则可能会导致哈希表冲突过多。

然后,构造的哈希表和std :: vector之间在内存使用率,搜索机制/性能或其他任何方面是否有所不同?

是的。在.reserve(N)之后,哈希表会为至少N个“存储桶”分配一个连续的内存块(基本上是一个数组)。如果考虑GCC实施,则N将四舍五入为一个素数。每个存储桶可以将迭代器存储到pair<int,data_type>个节点的前向链接列表中。

因此,如果您实际上将N个条目放入哈希表中,则说明...

  • 由> = N个元素组成的数组,大小为sizeof(forward-list-iterator)
  • N个内存分配为> = sizeof(pair<int,data_type>) + sizeof(next-pointer/iterator for forward-list)

... vector仅使用约N * sizeof(data_type)字节的内存:可能是哈希表使用的一小部分内存,并且是data_type的所有向量内存s是连续的,您很有可能会从与您当前尝试访问的元素相邻的CPU缓存元素中受益,从而使它们在以后访问时都快得多。

另一方面,如果您没有在哈希表中放入很多元素,则使用内存的主要功能是包含迭代器的存储桶阵列,这些存储桶通常是指针的大小(例如,每个指针的大小为32或64位) ,而data_type的向量-如果您也有reserve(N)-将已经分配了N * sizeof(data_type)字节的内存-用于大型data_type,它们可能比哈希大得多表。尽管如此,您仍然经常可以分配虚拟内存,并且如果您没有对内存页面进行故障处理而需要物理后备内存,则不会对程序或计算机造成有意义的内存使用或性能损失。 (至少对于64位程序,虚拟地址空间实际上是无限的。)

如果我知道肯定使用了3个按键0,5,9987,但是可能会或可能不会使用按键1〜9986。

如果我知道集合中没有键大于10000,那么使用大小为10000的std :: vector将保证O(1)访问随机数据的时间复杂度,但是会浪费内存。

在这种情况下,std :: unordered_map是否可以为该问题提供更好的解决方案? *我的意思是,这样一种解决方案可以节省尽可能多的内存,同时将时间复杂度保持在同一水平。

在这种情况下,如果您reversed(10000)在前面,而data_type并不比迭代器/指针大很多,那么unordered_map的各个方面都会变得更加糟糕。如果您不预先预留,则哈希表将仅为少数几个存储桶分配空间,并且您使用的虚拟地址空间将比具有10000个元素的vector少得多(即使{{1 }}是data_type)。

,

如果仅要打包3个元素,则最佳解决方案是使用 Call createPK("dbo_tblHotels","ID") :)比std::vector<std::pair<int,data_type>>(实际上分配了多个向量桶)占用的内存更少,并且查找性能更好由于常量很小,对于少量元素也是最佳选择。

对于较大的地图,std::unordered_map<int,data_type>O(1)都保证了std::vector<data_type>的复杂性,但是向量对std::unordered_map<int,data_type>的隐藏常数要低得多,因为它不会无需对照存储桶中的其他元素检查该元素。我建议始终使用向量,除非您没有足够的内存来适应它,在这种情况下,您可以使用O通过牺牲一点性能来节省内存。