variable-precision SWAR

简介

在Redis中的BITCOUNT命令可以实现统计一个key对应的二进制数组1的个数,它的实现方式便是查表法+variable-precision SWAR来提高效率。当位数小于28字节时使用查表法,大于等于28字节时使用variable-precision SWAR算法。

查表法

查表法比较简单,Redis使用8位的表记录了所有的情况,例如

0000 0000 0
0000 0001 1
。。。
1111 1110 7
1111 1111 8

对于小于28字节的数组,每8位查表一次,累加就是1的个数了

variable-precision SWAR

而对于超过28个字节的数组使用variable-precision SWAR算法,下面来介绍下该算法,首先看如下代码,它是计算一个32位数组的含有1的个数

uint32_t swar(uint32_t i) {
  //第一步
  i = (i & 0x55555555) + ((i >> 1) & 0x55555555);
  //第二步
  i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
  //第三步
  i = (i & 0x0f0f0f0f) + ((i >> 4) & 0x0f0f0f0f);
  //第四步
  i = (i * (0x01010101) >> 24);
  return i;
}

下面对每一步进行解析

  1. 第一步
    0x55555555 转换为二进制为:
    01010101 01010101 01010101 01010101
    注意到它的特点,偶数位为0,奇数位为1,那么与这个数与操作会得到什么呢?我们先把问题缩减下,考虑它的循环体为01,考虑两位的情况,所有的数有四种分别为00、01、10、11,分别于01与结果如下所示:
所有情况 01
00 00
01 01
10 00
11 01

它得到的是奇数位1的个数,如果把这四种情况都右移一位,那么得到的就是原数偶数位1的个数。所以把这个结论推到0x55555555
i & 0x55555555 在每两位上得到的是每两位奇数位的1的个数,而((i >> 1) & 0x55555555)在每两位上得到的是每两位偶数位的个数。二者相加得到的就是每两位上1的个数。

  1. 第二步
    0x33333333转换为二进制为:
    00110011 00110011 00110011 00110011
    在上面的对于与01的结果得到的是偶数位1的个数,个数的取值范围为00、01、10,那么每四位与0011得到就是低两位的1的个数,每四位右移两位后与0011得到的就是高两位的1的个数,二者相加得到的就是每4位的1的个数

  2. 第三步
    0x0f0f0f0f 转换为二进制为:
    00001111 00001111 00001111 00001111
    在第二步中得到的是每四位分组的1的个数,同理与00001111得到就是低四位的1的个数,每四位分组右移四位后与00001111得到的高四位的1的个数,二者相加得到的是每8位的1的个数

  3. 第四步
    0x01010101转换为二进制为:
    00000001 00000001 00000001 00000001
    第三步得到的每8位一组统计的1的个数,一共有4组,该值乘以00000001 00000001 00000001 00000001,举个例子
    假如32位数为:
    00000011 00000111 10000111 01000011
    00000001 00000001 00000001 00000001


00000011 00000111 10000111 01000011
00000111 10000111 01000011
10000111 01000011
01000011


注意下高8位已经变成了四个分组之和了,所以最终得到的就是32位数中1的个数了

redis中的实现

size_t redisPopcount(void *s, long count) {
    size_t bits = 0;
    unsigned char *p = s;
    uint32_t *p4;
    //8位的表用于查表
    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 28 bytes at a time */
    p4 = (uint32_t*)p;
    //如果超过28个字节,则使用variable-precision SWAR算法处理
    while(count>=28) {
        uint32_t aux1, aux2, aux3, aux4, aux5, aux6, aux7;
        //取出28个字节,每次取4个,取7次
        aux1 = *p4++;
        aux2 = *p4++;
        aux3 = *p4++;
        aux4 = *p4++;
        aux5 = *p4++;
        aux6 = *p4++;
        aux7 = *p4++;
        count -= 28;
        //下面两步计算四位分组1的个数
        aux1 = aux1 - ((aux1 >> 1) & 0x55555555);
        aux1 = (aux1 & 0x33333333) + ((aux1 >> 2) & 0x33333333);
        //下面两步计算四位分组1的个数
        aux2 = aux2 - ((aux2 >> 1) & 0x55555555);
        aux2 = (aux2 & 0x33333333) + ((aux2 >> 2) & 0x33333333);
        ////下面两步计算四位分组1的个数
        aux3 = aux3 - ((aux3 >> 1) & 0x55555555);
        aux3 = (aux3 & 0x33333333) + ((aux3 >> 2) & 0x33333333);
        ////下面两步计算四位分组1的个数
        aux4 = aux4 - ((aux4 >> 1) & 0x55555555);
        aux4 = (aux4 & 0x33333333) + ((aux4 >> 2) & 0x33333333);
        ////下面两步计算四位分组1的个数
        aux5 = aux5 - ((aux5 >> 1) & 0x55555555);
        aux5 = (aux5 & 0x33333333) + ((aux5 >> 2) & 0x33333333);
        //下面两步计算四位分组1的个数
        aux6 = aux6 - ((aux6 >> 1) & 0x55555555);
        aux6 = (aux6 & 0x33333333) + ((aux6 >> 2) & 0x33333333);
        ////下面两步计算四位分组1的个数
        aux7 = aux7 - ((aux7 >> 1) & 0x55555555);
        aux7 = (aux7 & 0x33333333) + ((aux7 >> 2) & 0x33333333);
        //计算8位分组1的个数,并使用乘法以及移位计算出总的1的个数
        bits += ((((aux1 + (aux1 >> 4)) & 0x0F0F0F0F) +
                    ((aux2 + (aux2 >> 4)) & 0x0F0F0F0F) +
                    ((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) +
                    ((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) +
                    ((aux5 + (aux5 >> 4)) & 0x0F0F0F0F) +
                    ((aux6 + (aux6 >> 4)) & 0x0F0F0F0F) +
                    ((aux7 + (aux7 >> 4)) & 0x0F0F0F0F))* 0x01010101) >> 24;
    }
    /* Count the remaining bytes. */
    //小于28个字节的使用查表法计算出
    p = (unsigned char*)p4;
    while(count--) bits += bitsinbyte[*p++];
    return bits;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容

  • 1 关键字 1.1 关键字的概述 Java的关键字对java的编译器有特殊的意义,他们用来表示一种数据类型,或...
    哈哈哎呦喂阅读 650评论 0 0
  • 背景 在java中float赋值给double,会产生精度问题。 输出为2.0999999046325684。 小...
    我叫小小强阅读 19,215评论 2 23
  • 网站乱码问题我们会经常碰到,大多见于非英文的中文字符或其他字符乱码,而且,这类问题常常是因为编码方式问题,主要原因...
    波段顶底阅读 2,852评论 1 9
  • Java 中位运算符有与(&)、或(|)、非(~)、异或(^)、左移(<<)、右移(>>)、无符号右移(>>>),...
    JohnnyShieh阅读 1,099评论 0 0
  • 最近我的情绪和生活都进入了低谷,我被我自以为是的爱情观狠狠的打了脸。我开始正视自己的内心真正的想法,等我理顺这一切...
    kingsley兮阅读 257评论 0 0