怎么样实现较快的Hash Table

2021-01-11 19:22:27 字数 5035 阅读 7728

1楼:福喜

我们服务器一直在用boost/sgl stl的hash table,但是从来没有考虑过其中的效率问题,虽然hash_map/unordered_map跑的可能真的比map快一些,可能应该不是你理解的那么快.其实他可以更快一些!!!

当我自己尝试着实现了一个hash table之后,我发现确实如此.这篇文章也是来说说,如何实现较快的一个.

通常的hash table都是用开链法,开放地址法来解决冲突.开链法是总容易实现的一个,而且因为效率稳定,被加入了c++11,取名unordered_map.不过效率实在不咋地.

开放地址法的hash table,我是从google-sparsehash里面注意到的,虽然数据结构,算法导论都会讲到.网上说速度很快,我就去看了一下api,其比普通的unordered_map多了一组api:

1. set_empty_key/set_deleted_key

在开链法中,所有的节点都是容器内的内容,可是开放地址法中不是的.所以需要额外的信息来维护节点的可用性信息.

当时我看到这两个api,大概就猜到内存是怎么实现的,闲来无事就是试着写了一个demo,在vc 2008下面跑的结果是,比unordered_map快一倍多;在linux x64 gcc 4.4下面的结果是,比unordered_map快了将近1倍.

2. 高性能的hash table必须是开放地址法的

这么说,是有原因的.链表的特性就是容易删除,容易插入.可是hash table不需要这些特性,hash table只需要快.

可以链表这东西,偏偏做不到快速定位,虽然你知道有下一个节点,但是你不知道下一个节点的准确位置,经常会造成缓存未命中,浪费大量时间.

3. bucket的容量

bucket的容量也是影响hash table性能的一个因素.无数的数据结构和算法书籍,都教导大家,通过质数取余数,可以获得比较好的下标分布.可是,无论是除法还是乘法,消耗都是相当高的.

十几个或者几十个时钟周期,始终比不上一两个时钟周期快.所以,高性能的hash table必须要把bucket的容量设置成2^n.google-sparsehash里面初始容量是32.

扩容的话,都是直接左移;算下标的话,都是(容量-1) & hash_value,简单的一个位运算搞定.

4. 正确实现find_position

我自己实现的hash table,是线性探测法的.所以find position也是比较简单,就是通过hash value和掩码,获取到其实下标,然后一个一个test.需要把buckets当作是环形的,否则buckets最末位的数据冲突就会不好搞.

(我当时没有考虑这一点,直接给他扩容了.....)

5. 对象模型

不同的key和value模型,可以导致你对hash table的不同实现.简单的说,在c里面,你可以不用考虑key和value的生命周期(:d),但是c++里面,你不得不考虑key,value的生命周期问题.

你不能做一个假设,key和value都是简单数据类型.一个int映射到一个对象,这种经常会用到的.

所以,erase一个key的时候,需要把key设置成deleted,然后还要把value重置一遍.如果没有重置,对象所引用的内存有可能就会被泄露.

这引发了我另外一个想法,就是通过模板,来特化value的reset行为.因为不是所有的value都是需要被重置的,只有那些复杂对象,才需要.

6. 可以考虑缓冲hash value

如果key都是简单数据,而非string或者复杂的数据类型,缓冲是没有任何意义的,因为hash value可以被快速的计算出来;但是当key是char*,或者一些复杂的数据类型,缓冲就会变的有意义.而且缓冲更有利于重排,容器扩容的时候速度会更快一些.

7. 考虑使用c的内存分配器

尽量不要使用c++的new/delete来分配内存.new,delete会有对象的构造,析构过程,这可能不是你所希望的.针对key和value数据类型的不同,你可能会有自己的特有的构造,析构过程.

而且,c的内存分配器,同样可以被一些第三方库优化,比如tcmalloc/jemalloc等.

8. 选一个好的hash函数(这是最重要的)

9. 尽力防止拷贝

rehash非常耗时,如果支持c++11,就使用move操作;如果不支持,就用swap,否则会复制很多次.

哈希表map和table哪个性能高?

2楼:我就叫对方

list支持快

速的插入和删除,但是查找费时;

vector支持快速的查找,但是插入费时。

map查找的时间复杂度是对数的,这几乎是最快的,hash也是对数的。

如果我自己写,我也会用二叉检索树,它在大部分情况下可以保证对数复杂度,最坏情况是常数复杂度,而std::map在任何情况下都可以保证对数复杂度,原因是它保证存诸结构是完全二叉检索树,但这会在存诸上牺牲一些时间。

stl 中的 map 内部是平衡二叉树,所以平衡二叉树的性质都具备。查找数据的时间也是对数时间。 vector,在分配内存上一般要比 new 高效的多。

为什么说 hash_map 是对数级的?在不碰撞的情况下,hash_map是所有数据结构中查找最快的,它是常数级的。

如果对问题设计了足够好的hash算法,保证碰撞率很低,hash_map的查找效率无可置疑。

另外,stl的map,它的查找是对数级的,是除hash_map外最高的了,你可以说“也许还有改进余地”,但对于99.9999%的程序员,设计一个比stl map好的map,我执悲观态度。

stl的map有平衡策略(比如红黑树什么的),所以不会退化,不需要考虑数据本身的分布问题。只不过,如果数据本身是排好序的,用vector或heap会明显的快些,因为它们的访问比较简单。

我想没必要怀疑stl::map的查找效率,影响效率最主要的因素是什么?算法,在查找问题上,有什么算法比rb_tree更好吗?

至少现在还没有。不否 认你可以通过自己写**,设计一个符合你需要的br—tree,比stl::map简捷那么一点,但最多也就每次迭代中少一行指令而已,处理十万个数据多 执行十万行指令,这对你重要吗?

如果你不是在设计os像linux,没人会关注这十万行指令花的时间。

rb-tree的时间花在了插入和删除上,如果你不是对插入和删除效率要求很高,你没有理由不选择基于rb-tree的stl::map。

大多数程序员写不出比std::map更好的map,这是当然的。然而并不是std::

map的所有特性都出现在我们的程序中,自己编写的可以更适合自己的程序,的确会比std::map更快一些。

关于hash_map,它与map的实现机制是不一样的,map内部一般用树来实现,其查找操作是o(logn)的,这个没有争议,我就不多说了。

hash_map的查找,内部是通过一个从key到value的运算函数来实现的,这个函数“只接受key作为参数”,也就是说,hash_map的查找 算法与数据量无关,所以认为它是o(1)级的。来这里的应该都是达人,可以参看《数据结构》。当然,事实总不这样完美,再引一段前面我自已说的话,进一步 说明,以免误会:

-----------------------------------------

在不碰撞的情况下,hash_map是所有数据结构中查找最快的,它是常数级的。

------------------------------------------

注意我的前提:“在不碰撞的情况下”,其实换句话说,就是要有足够好的hash函数,它要能使key到value的映射足够均匀,否则,在最坏的情况下,它的计算量就退化到o(n)级,变成和链表一样。

如果说 hash_map 是所有容器中最慢的,也只能说:“最拙劣的hash函数”会使hash_map成为查找最慢的容器。但这样说意义不大,因为,最凑巧的排列能使冒泡排序成为最快的排序算法。

bs: "对于大型容器而言,hash_map能够提供比map快5至10倍的元素查找速度是很常见的,尤其是在查找速度特别重要的地方.另一方面,如果hash_map选择了病态的散列函数,他也可能比map慢得多.

"ansic++在1998年之后就没再有重大改变,并且决定不再向c++标准库中做任何重大的变更,正是这个原因,hash table(包括hash_map)并没有被列入标准之中,虽然它理应在c++标准之中占有一席之地。

虽然,现在的大多数编译平台支持hash table,但从可移植性方面考虑,还是不用hash table的好。

hehe俺也来凑凑热闹。

1.有的时候vector可以替代map

比如key是整数,就可以以key的跨度作为长度来定义vector。

数据规模很大的时候,差异是惊人的。当然,空间浪费往往也惊人。

2.hash是很难的东西

没有高效低碰撞的算法,hash_***没有意义。

而对不同的类型,数据集,不可能有优良的神仙算法。必须因场合而宜。

俺有的解决方法是gp,可不是饭型,是遗传编程,收效不错。

你的百万级的数据放到vector不大合适。因为vector需要连续的内存空间,显然在初始化这个容器的时候会花费很大的容量。

使用map,你想好了要为其建立一个主键吗?如果没有这样的需求,为什么不考虑deque或者list?

map默认使用的是deque作为容器。其实map不是容器,拿它与容器比较意义不大。因为你可以配置它的底层容器类型。

如果内存不是考虑的问题。用vector比map好。map每插入一个数据,都要排序一次。所以速度反不及先安插所有元素,再进行排序。

用 binary_search对已序区间搜索,如果是随机存取iterator,则是对数复杂度。可见,在不考虑内存问题的情况下,vector比map 好。

如果你需要在数据中间进行插入,list 是最好的选择,vector 的插入效率会让你痛苦得想死。

涉及到查找的话用map比较好,因为map的内部数据结构用rb-tree实现,而用vector你只能用线性查找,效率很低。

stl还提供了 hash容器,理论上查找是飞快~~~。做有序插入的话vector是噩梦,map则保证肯定是按key排序的,list要自己做些事情。

hash类型的查找肯定快,是映射关系嘛,但是插入和删除却慢,要做移动操作, list类型的使链式关系,插入非常快,但是查找却费时,需要遍历~~ , 还是用list类型的吧,虽然查找慢点,

先快速排序,然后二分查找,效率也不低

现实中的魔女是什么样子啊,真实世界有魔女吗?亲快!!!!

1楼 留香北辰 魔女幼熙。。。 就这样子的 http static9 photo sina orignal 4a9432f0c76858bb2f788 喜欢就给我分啊老大 2楼 andy神 唲禳 我认为魔女还是有的,虽然魔法什么的不太现实,但是如果思想和心灵是魔女级别的话,也可以算是魔女啦 真实世界...

家风有怎样的现实意义,重提家风有怎样的现实意义

1楼 用打户 家风,怎么说呢,就是教养吧,没教养的人,和有教养的,你心里想想就知道了,我不知道该怎么说 在新时代重提家风有怎样的现实意义 2楼 知道de太少 家庭是社会的最为基本的细胞。在这个意义上,我们可以说,任何时代的家庭都需要一种普遍的家庭道德。传统中国人所提倡的孝老爱亲的家庭道德就是这一类。...

微博实时号,是怎么做到的呀,微博实时号如何快速实现? 50

1楼 匿名用户 1 随时随地传播信息 2 传播方式呈裂变 3 信息交互简便快捷 就和以前想着让 升级快类似,相信自己努力的话一定会很快的。希望可以对你有所帮助。 参考资料 微博实时号如何快速实现? 50 2楼 回忆那么深 实时号的养成最快也要一周左右,最主要的原因还是你自己,是否坚持微博发文,以及保...