先上源码,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个。