A recent talk about unordered_map in C++ made me realize that I should use unordered_map for most cases where I used map before, because of the efficiency of lookup ( amortized O(1) vs. O(log n) ). Most times I use a map, I use either int or std::string as the key type; hence, I've got no problems with the definition of the hash function. The more I thought about it, the more I came to realize that I can't find any reason of using a std::map over a std::unordered_map in the case of keys with simple types -- I took a look at the interfaces, and didn't find any significant differences that would impact my code.
因此,问题是:是否有任何真正的理由使用std::map而不是std::unordered_map在简单类型的情况下,如int和std::string?
我是从严格的编程角度提出这个问题的——我知道它并不是完全标准的,而且它可能会给移植带来问题。
另外,我认为正确答案之一可能是“对于较小的数据集更有效”,因为开销更小(是真的吗?)——因此,我希望将这个问题限制在键数量非普通的情况下(>1 024)。
编辑:哦,我忘记了显而易见的(感谢GMan!)——是的,地图当然是有序的——我知道,我正在寻找其他原因。
我大致同意GMan的观点:根据使用类型的不同,std::map可以(而且通常)比std::tr1::unordered_map快(使用VS 2008 SP1中包含的实现)。
有几个复杂的因素需要记住。例如,在std::map中,您正在比较键,这意味着您只查看足够多的键的开头,以区分树的左右子分支。根据我的经验,几乎只有当你使用int这样可以在单个指令中进行比较的时候,你才会查看整个键。对于更典型的键类型,如std::string,通常只比较几个字符。
相比之下,一个像样的哈希函数总是查看整个键。IOW,即使查找表的复杂度是恒定的,哈希本身也具有大致的线性复杂度(尽管是键的长度,而不是项的数量)。使用长字符串作为键,std::map可能会在unordered_map开始搜索之前完成搜索。
其次,虽然有几种方法可以调整哈希表的大小,但大多数方法都非常慢——除非查找比插入和删除频繁得多,否则std::map通常会比std::unordered_map快。
当然,就像我在对你上一个问题的评论中提到的,你也可以使用树表。这既有优点也有缺点。一方面,它将最坏的情况限制在一棵树上。它还允许快速插入和删除,因为(至少当我这样做时)我使用了固定大小的表。消除所有的表大小调整可以让你的哈希表更简单,通常更快。
另一点:哈希和基于树的映射的需求是不同的。哈希显然需要一个哈希函数和一个相等比较,其中有序映射需要一个小于比较。当然,我提到的混合型需要两者兼备。当然,对于使用字符串作为键的常见情况,这并不是真正的问题,但某些类型的键比哈希更适合排序(反之亦然)。
不要忘记map保持其元素的顺序。如果你不能放弃它,显然你不能使用unordered_map。
另外需要记住的一点是,unordered_map通常会使用更多的内存。Map只有一些内部指针和每个对象的内存。相反,unordered_map有一个大数组(在某些实现中会变得相当大),然后为每个对象提供额外的内存。如果需要内存感知,map应该会更好,因为它缺少大数组。
所以,如果你需要纯粹的查找-检索,我认为unordered_map是最好的方法。但总会有权衡,如果你负担不起,那你就不能使用它。
仅凭个人经验,我发现在主实体查找表中使用unordered_map而不是map时,性能有了巨大的改进(当然是度量的)。
另一方面,我发现它在重复插入和删除元素时要慢得多。它非常适合相对静态的元素集合,但如果您正在进行大量的插入和删除,那么哈希+桶似乎就会累加起来。(注意,这需要经过多次迭代。)
如果你想比较std::map和std::unordered_map实现的速度,你可以使用谷歌的sparsehash项目,它有一个time_hash_map程序来计时。例如,在x86_64 Linux系统上使用gcc 4.4.2
$ ./time_hash_map
TR1 UNORDERED_MAP (4 byte objects, 10000000 iterations):
map_grow 126.1 ns (27427396 hashes, 40000000 copies) 290.9 MB
map_predict/grow 67.4 ns (10000000 hashes, 40000000 copies) 232.8 MB
map_replace 22.3 ns (37427396 hashes, 40000000 copies)
map_fetch 16.3 ns (37427396 hashes, 40000000 copies)
map_fetch_empty 9.8 ns (10000000 hashes, 0 copies)
map_remove 49.1 ns (37427396 hashes, 40000000 copies)
map_toggle 86.1 ns (20000000 hashes, 40000000 copies)
STANDARD MAP (4 byte objects, 10000000 iterations):
map_grow 225.3 ns ( 0 hashes, 20000000 copies) 462.4 MB
map_predict/grow 225.1 ns ( 0 hashes, 20000000 copies) 462.6 MB
map_replace 151.2 ns ( 0 hashes, 20000000 copies)
map_fetch 156.0 ns ( 0 hashes, 20000000 copies)
map_fetch_empty 1.4 ns ( 0 hashes, 0 copies)
map_remove 141.0 ns ( 0 hashes, 20000000 copies)
map_toggle 67.3 ns ( 0 hashes, 20000000 copies)