使用构造哈希表的方法来代替简单的遍历查找
是常用的算法优化
根据关键字计算哈希值的时候
通常使用取模运算计算最终的桶下标
以防止桶的溢出
在Linux内核中也使用了大量的哈希表进行查找
起初内核中也是使用取模的方式计算桶下标
不过现在内核中在实现哈希表的时候
桶通常选择为2^n个
使用按位与(2^n - 1)的方式计算桶下标
最终的目的都是为了确定桶下标
但是内核为什么选择位与的方式还是值得考究的
使用一个小程序andmod.c来看看位与和取模的区别
int
main(void)
{
int a = 0x11;
int b = 0x22;
int c = 0x33;
int d = 0x44;
c = a & b;
d = a % b;
return 0;
}
直接使用gcc -o andmod andmod.c来编译这段小程序
不要使用-O选项进行优化
因为这段代码本质上是无用的
会被编译器优化掉
然后使用objdump -d andmod来查看汇编代码
程序中使用一些诸如0x11这样的特殊值有助于对反汇编出的代码进行分析
重点查看main函数这段就可以了
08048394 <main>:
8048394: 55 push %ebp
8048395: 89 e5 mov %esp,%ebp
8048397: 83 ec 10 sub $0x10,%esp
804839a: c7 45 f0 11 00 00 00 movl $0x11,-0x10(%ebp) # 局部变量 a = 0x11
80483a1: c7 45 f4 22 00 00 00 movl $0x22,-0xc(%ebp) # b = 0x22
80483a8: c7 45 f8 33 00 00 00 movl $0x33,-0x8(%ebp) # c = 0x33
80483af: c7 45 fc 44 00 00 00 movl $0x44,-0x4(%ebp) # d = 0x44
80483b6: 8b 45 f4 mov -0xc(%ebp),%eax # b存入寄存器eax
80483b9: 8b 55 f0 mov -0x10(%ebp),%edx # a存入寄存器edx
80483bc: 21 d0 and %edx,%eax # 位与运算,结果在寄存器eax内
80483be: 89 45 f8 mov %eax,-0x8(%ebp) # 结果存入c
80483c1: 8b 45 f0 mov -0x10(%ebp),%eax # 构造被除数,a存入寄存器eax
80483c4: 89 c2 mov %eax,%edx # a存入寄存器edx
80483c6: c1 fa 1f sar $0x1f,%edx # 算术右移31位
80483c9: f7 7d f4 idivl -0xc(%ebp) # 除数b,被除数为[%edx][%eax]
80483cc: 89 55 fc mov %edx,-0x4(%ebp) # 余数存入d,即取模运算的结果
80483cf: b8 00 00 00 00 mov $0x0,%eax # 返回值0存入寄存器eax
80483d4: c9 leave
80483d5: c3 ret
可以看到位与使用and指令计算
而取模是通过除法运算指令idivl(这里是有符号32位除法)
取其余数来计算的
根据网上的一个指令周期资料Coding_ASM_-_Intel_Instruction_Set_Codes_and_Cycles.pdf
以表中Pentium的数据为例
位与计算使用了2次mov指令和1次and指令
共需3个CPU周期
取模运算使用了2次mov指令、1次sar指令和1次idivl指令
共需52个CPU周期
当然这个指令周期表也只是参考而已
至于不同的机器不同的编译器优化后差距多少
就要具体问题具体分析了
如此一看内核中使用位与运算代替取模运算的原因不言自明
节约了CPU周期提升了整体性能
当哈希表的查找中桶下标计算很频繁的时候
由此节约的CPU周期还是很可观的