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的个数,
经过步骤一的结果:

二进制数 分组
0x2B4A1F87 00 10 10 11 01 00 10 10 00 01 11 11 10 00 01 11
0x16451A46 00 01 01 10 01 00 01 01 00 01 10 10 01 00 01 10
1的个数 0 1 1 2 1 0 1 1 0 1 2 2 1 0 1 2

经过步骤二的结果:

二进制数 分组
0x2B4A1F87 0010 1011 0100 1010 0001 1111 1000 0111
0x13121413 0001 0011 0001 0010 0001 0100 0001 0011
1的个数 1 3 1 2 1 4 1 3

经过步骤三的结果:

二进制数 分组
0x2B4A1F87 00101011 01001010 00011111 10000111
0x04030504 00000100 00000011 00000101 00000100
1的个数 4 3 5 4

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

二进制数 24~31位 16~23位 8~15位数 0~7位
0x100C0904 00010000 00001100 00001001 00000100
1的个数 16 / / /

右移24位的结果:

二进制数 24~31位 16~23位 8~15位数 0~7位
0x10 00000000 00000000 00000000 00010000

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

你可能感兴趣的:(redis)