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

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) //步骤1
aux1 = (aux1&0x33333333)+((aux>>2)&0x33333333) //步骤2
aux1 = (aux1&0x0F0F0F0F)+((aux>>4)&0x0F0F0F0F)  //步骤3
aux1 = ((aux1*0x01010101)>>24) //步骤4

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

二进制数分组
0x2B4A1F8700101011010010100001111110000111
0x16451A4600010110010001010001101001000110
1的个数0112101101221012

经过步骤二的结果:

二进制数分组
0x2B4A1F8700101011010010100001111110000111
0x1312141300010011000100100001010000010011
1的个数13121413

经过步骤三的结果:

二进制数分组
0x2B4A1F8700101011010010100001111110000111
0x0403050400000100000000110000010100000100
1的个数4354

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

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

右移24位的结果:

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值