Redis源码解析——统计二进制数中1的个数

来源:互联网 发布:内外网络切换器软件 编辑:程序博客网 时间:2024/06/06 06:34

Redis源码解析——统计二进制数中1的个数

先上源码,redisPopcount函数在bitops.c中:

size_t redisPopcount(void *s, long count) {    size_t bits = 0;    unsigned char *p = s;    uint32_t *p4;    static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};    /* Count initial bytes not aligned to 32 bit. */    while((unsigned long)p & 3 && count) {        bits += bitsinbyte[*p++];        count--;    }    /* Count bits 16 bytes at a time */    p4 = (uint32_t*)p;    while(count>=16) {        uint32_t aux1, aux2, aux3, aux4;        aux1 = *p4++;        aux2 = *p4++;        aux3 = *p4++;        aux4 = *p4++;        count -= 16;        aux1 = aux1 - ((aux1 >> 1) & 0x55555555);        aux1 = (aux1 & 0x33333333) + ((aux1 >> 2) & 0x33333333);        aux2 = aux2 - ((aux2 >> 1) & 0x55555555);        aux2 = (aux2 & 0x33333333) + ((aux2 >> 2) & 0x33333333);        aux3 = aux3 - ((aux3 >> 1) & 0x55555555);        aux3 = (aux3 & 0x33333333) + ((aux3 >> 2) & 0x33333333);        aux4 = aux4 - ((aux4 >> 1) & 0x55555555);        aux4 = (aux4 & 0x33333333) + ((aux4 >> 2) & 0x33333333);        bits += ((((aux1 + (aux1 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +                ((((aux2 + (aux2 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +                ((((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +                ((((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24);    }    /* Count the remaining bytes. */    p = (unsigned char*)p4;    while(count--) bits += bitsinbyte[*p++];    return bits;}

可以看出,Redis是根据输入的二进制数的大小选择使用不同方法统计数组中1的个数。如果二进制数的位数大于128位,即16个字节,那么首先使用variable-precision SWAR算法计算二进制数中超出128位部分中1的数量,然后再使用查表算法计算剩余部分中1的数量。
1.查表算法
 (1)构建查询表bitsinbyte数组,查询表中的每一个元素表示一个8位二进制数中1的个数,按升序排列(0-255),比如bitsinbyte[0]与0x00对应,bitsinbyte[1]与0x01对应,bitsinbyte[255]与0xFF对应;
 (2)将输入的二进制位数组转换为无符号字符数组(unsigned char*),然后每次取8位,作为bitsinbyte数组的下标,得到这个8位二进制数中1的个数,累加起来即得二进制数中1的个数。
2.variable-precision SWAR算法
在Redis的实现中,每循环一次,计算32*4位的二进制数中1的个数。Redis使用的是处理32位二进制位数组的variable-precision SWAR算法,算法如下,这样写容易理解一些:

aux1 = (aux1&0x55555555)+((aux>>1)&0x55555555) //步骤1aux1 = (aux1&0x33333333)+((aux>>2)&0x33333333) //步骤2aux1 = (aux1&0x0F0F0F0F)+((aux>>4)&0x0F0F0F0F)  //步骤3aux1 = ((aux1*0x01010101)>>24) //步骤4

利用了归并的思想。
步骤1是计算每两位二进制数中1的个数
步骤2是计算每四位二进制数中1的个数
步骤3是计算每八位二进制数中1的个数
步骤4是将之前计算的每八位二进制数中1的个数相加,并移至最低位八位
举个实例,帮助理解:
统计0x2B4A1F87中1的个数,
经过步骤一的结果:

二进制数分组0x2B4A1F87001010110100101000011111100001110x16451A46000101100100010100011010010001101的个数0112101101221012

经过步骤二的结果:

二进制数分组0x2B4A1F87001010110100101000011111100001110x13121413000100110001001000010100000100111的个数13121413

经过步骤三的结果:

二进制数分组0x2B4A1F87001010110100101000011111100001110x04030504000001000000001100000101000001001的个数4354

经过步骤四的结果:
aux1*0x01010101的结果:

二进制数24~31位16~23位8~15位数0~7位0x100C0904000100000000110000001001000001001的个数16///

右移24位的结果:

二进制数24~31位16~23位8~15位数0~7位0x1000000000000000000000000000010000

最终统计出的结果就是16个。

0 0
原创粉丝点击