如果使用数据库,毫无疑问redis的有序集合(基于跳表)是最简单的
一般来讲很多文章也会推荐跳表skiplist
但是由于战力可转化为整数数值存储,论本地内存计算的数据结构,当属字典树最优
标准树容器都不支持容器内节点的字典序的距离差计算,但其实自己改造树是可以的实现的:每个分支记录分支下元素总数,每当有元素插入删除时,更新其所有祖先上的数量记录,用时O(层数)=O(log(N))
(4、8、16叉……)字典树,可以通过两个键值的异或运算快速找到最小公共父节点,层内定位是随机访问,且无需遍历比较直接用位运算定位节点,局部内存缓存效果超级好,数量加减都在树结构上完成。对比基于对比和概率的跳表来说各方面都优秀的多。
实测百万数据, 32叉字典树 对比高度优化的跳表,根据键值查找对象的性能快25倍,插入和rank性能都快约5~7倍(注意插入时内存申请的耗时已经达到不可忽视的程度)。
104万个数据(数值范围0~104万),操作104万次,相同release编译,每组测试交叉并在之间清除cpu内存缓存
Redis zsortedset源码抽取版(保留数据结构不变,脱离原工作环境,轻度改为模板类)
高度优化的跳表(估计理论极限性能85%+以上,比git上前4的skiplist实现快40%到150%,在含rank的完善实现中估计排行第一) ,
和32叉字典树对比:
redis sortedset 源码抽取版 double键 | 超快速跳表(15层) 支持重复键 元素总量超(2^层数)时自适应调整概率 元素存储于对应节点 int键(比double键快5%) | 32叉字典树 (含排位功能,半支持重复键值) int键(不支持其他类型) | |
---|---|---|---|
随机插入 | 2434ms | 827ms 234ms(稳定排序(严格要求按顺序插入时)+最佳建表) 171ms(不稳定排序(不要求同键顺序时)+最佳建表) 312ms(分16组不稳定排序+并集) 77ms(从有序数据源最佳建表=创建跳表副本) | 140ms(32个小元素集成至底层节点) 218ms(为每个元素分配空间) |
随机顺序 查找>=key的第一个对象 | 2511ms | 890ms 390ms(最佳表) | 47ms |
值查排位 | 2589ms | 890ms(与链表头部距离) 406ms(最佳表) | 203ms(排位) 390ms(随机两键值距离算法) |
排位查值 | 343ms | 156ms 46ms(最佳表) | 78ms |
随机删除 | 2153ms | 702ms(N次删除,每次删除键对应的第一个值) 818ms(N次删除,每次删除键对应的全部值(测试集只对应一个值)) 593ms(最佳表 删除键第一个) 421ms(随机顺序删除N/2次,每次正好能删除两个值) | 141ms(小类型集成) 171ms(为每个元素new) |
new int {i} 104万次 耗时41ms
std哈希表 插入{i,i} 104万次 234ms,1677万次5320ms。
1677万个数据(数值范围0~1677万),操作1677万次。(16倍数据量和16倍操作)
括号内的倍率指与104万测试相比。
redis源码抽取版 | 超快速跳表24层 | 32叉字典树 | |
---|---|---|---|
插入 | 75099ms | 28033ms(元素存储于链表节点)(28.19倍) 6193ms(稳定排序+最佳建表) 5288ms(分16组不稳定排序+并集) 2792ms(不稳定排序+最佳建表) 1170ms(从有序数据源最佳建表=创建跳表副本) | 4820ms(32个小元素集成至底层节点) (34.42倍) 6100ms(为每个元素分配空间) (27.98倍) |
查找>=key的第一个对象 | 76591ms | 29625ms (30.03倍) 12995ms(最佳表) | 1607ms (34.19倍) |
值查排位 | 81589ms | 30171ms(与链表头部距离) (33.01倍) 13495ms(最佳表) | 5553ms(排位) (27.35倍) 10655ms(随机两键后算距离) (27.32倍) |
排位查值 | 7004ms | 3027ms(17.70倍) 1014ms(最佳表) | 1513ms |
删除 | 73024ms | 28969ms(随机顺序查找键的第一个并删除)(35.72倍) | 5024ms(小类型集成) (35.63倍) 5679ms(为每个元素new) (33.2105倍) |
1677万元素乱序,每次取104万排序后再合并到跳表中,16次,共计 5288ms。
不含排序的每次合并104万有序数据耗时(ms):
95,129,147,155,184,210,217,241,267,261,274,294,305,312,338,333
超快速跳表提供了 修改方面:更新key而不析构value;查询方面:排位(下标)→键值对,键/迭代器→排位(下标)这两个std::map不支持的功能。