哈希表
在使用key, value场景的时候,最常用的一个数据结构,能够实现O(1)时间复杂度的算法。而我们常用的算法中,一般是使用一个比目标空间略大一些的质数,通过对key做一次编码函数,得到一个整数值,再找到对应的目标空间的slot位置,进而做插入操作。如下所示:
质数不妨假设为MAX
id = fun(key),
slot_id = id % MAX
找到对应的slot位置进行查找或者插入修改操作。
注意到:这里用的是取模操作。
另一种做法
最近在读redis对应底层存储结构哈希表的时候,看到了另一种类似,但不同的做法。做法如下:采用一个2的N次减1方作为存储空间。对应的key还是用一个编码函数,获得一个id值。再通过id值,求得slot。具体如下所示:
空间大小不妨假设为MAX
id = fun(key),
slot_id = id & MAX
注意到:这里用的是逻辑与操作,获取slot_id。
有何区别
刚开始的时候,觉得没什么区别,不就是一个运算不一样么,redis何苦如此麻烦。仔细一想,不太对!对于自己一眼不能看明白的事情,必须内心保持一丝敬意,才有可能会有长进。别人故意绕个弯,肯定有他的原因,很可能是你的水平不够,redis的设计者的追求极简和效率的思想,怎么可能搞这么麻烦的事情。
分析
区别就在于逻辑运算,与除法运算的区别。
回到计算机最基本的原理。在CPU的基本原理之中,CPU根本就不认识什么加减乘除,它只认识0和1,而且只认识逻辑运算。所谓的数据运算,只不过是转化为多次逻辑运算得到的结果。能想到这一步,就说明大学的计算机组成原理没有还给老师!
因此,从理论上分析,那逻辑与的效率,肯定是要比除法的效率要高,因为逻辑运算是CPU底层就支持的运算方式 。内心感叹,这就是普通工程师,与真正优秀的工程的差距。
求证
有了理论分析,下来自己通过简单的代码验证一下自己的猜想。
以下是逻辑与运算代码
#include <stdio.h>
int main(int argc, char** argv) {
const int MAX_LOOP = 100000000;
int res = 0;
for (int i = 0; i < MAX_LOOP; ++i) {
int a = 3;
int b = 7;
res = a & b;
}
printf("res = %d\n", res);
return 0;
}
取模操作代码如下:
#include <stdio.h>
int main(int argc, char** argv) {
const int MAX_LOOP = 100000000;
int res = 0;
for (int i = 0; i < MAX_LOOP; ++i) {
int a = 3;
int b = 7;
res = a / b;
}
printf("res = %d\n", res);
return 0;
}
在我的linux测试机器上,gcc 4.8.5,CentOS Linux release 7.3.1611 (Core)
测试结果如下:
1、求模方式结果:
real 0m0.354s
user 0m0.351s
sys 0m0.001s
2、逻辑与方案结果:
real 0m0.232s
user 0m0.230s
sys 0m0.001s
从结果来看,逻辑与操作要比求模要快上不少,耗时少50%左右。
总结
从这种微小的细节看出,真正优秀的工程师,是在所有可以优化的地方,都尽可能做到最优,这才是优秀与普通水平的差距!努力前行!