variable-precision SWAR

簡介

在Redis中的BITCOUNT命令可以實(shí)現(xiàn)統(tǒng)計(jì)一個(gè)key對(duì)應(yīng)的二進(jìn)制數(shù)組1的個(gè)數(shù)始绍,它的實(shí)現(xiàn)方式便是查表法+variable-precision SWAR來提高效率食店。當(dāng)位數(shù)小于28字節(jié)時(shí)使用查表法榛鼎,大于等于28字節(jié)時(shí)使用variable-precision SWAR算法。

查表法

查表法比較簡單,Redis使用8位的表記錄了所有的情況,例如

0000 0000 0
0000 0001 1
片迅。。皆辽。 柑蛇。
1111 1110 7
1111 1111 8

對(duì)于小于28字節(jié)的數(shù)組芥挣,每8位查表一次,累加就是1的個(gè)數(shù)了

variable-precision SWAR

而對(duì)于超過28個(gè)字節(jié)的數(shù)組使用variable-precision SWAR算法,下面來介紹下該算法耻台,首先看如下代碼空免,它是計(jì)算一個(gè)32位數(shù)組的含有1的個(gè)數(shù)

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;
}

下面對(duì)每一步進(jìn)行解析

  1. 第一步
    0x55555555 轉(zhuǎn)換為二進(jìn)制為:
    01010101 01010101 01010101 01010101
    注意到它的特點(diǎn),偶數(shù)位為0粘我,奇數(shù)位為1,那么與這個(gè)數(shù)與操作會(huì)得到什么呢痹换?我們先把問題縮減下征字,考慮它的循環(huán)體為01,考慮兩位的情況娇豫,所有的數(shù)有四種分別為00匙姜、01、10冯痢、11氮昧,分別于01與結(jié)果如下所示:
所有情況 01
00 00
01 01
10 00
11 01

它得到的是奇數(shù)位1的個(gè)數(shù),如果把這四種情況都右移一位浦楣,那么得到的就是原數(shù)偶數(shù)位1的個(gè)數(shù)袖肥。所以把這個(gè)結(jié)論推到0x55555555
i & 0x55555555 在每兩位上得到的是每兩位奇數(shù)位的1的個(gè)數(shù),而((i >> 1) & 0x55555555)在每兩位上得到的是每兩位偶數(shù)位的個(gè)數(shù)振劳。二者相加得到的就是每兩位上1的個(gè)數(shù)椎组。

  1. 第二步
    0x33333333轉(zhuǎn)換為二進(jìn)制為:
    00110011 00110011 00110011 00110011
    在上面的對(duì)于與01的結(jié)果得到的是偶數(shù)位1的個(gè)數(shù),個(gè)數(shù)的取值范圍為00历恐、01寸癌、10,那么每四位與0011得到就是低兩位的1的個(gè)數(shù)弱贼,每四位右移兩位后與0011得到的就是高兩位的1的個(gè)數(shù)蒸苇,二者相加得到的就是每4位的1的個(gè)數(shù)

  2. 第三步
    0x0f0f0f0f 轉(zhuǎn)換為二進(jìn)制為:
    00001111 00001111 00001111 00001111
    在第二步中得到的是每四位分組的1的個(gè)數(shù),同理與00001111得到就是低四位的1的個(gè)數(shù)吮旅,每四位分組右移四位后與00001111得到的高四位的1的個(gè)數(shù)溪烤,二者相加得到的是每8位的1的個(gè)數(shù)

  3. 第四步
    0x01010101轉(zhuǎn)換為二進(jìn)制為:
    00000001 00000001 00000001 00000001
    第三步得到的每8位一組統(tǒng)計(jì)的1的個(gè)數(shù),一共有4組庇勃,該值乘以00000001 00000001 00000001 00000001氛什,舉個(gè)例子
    假如32位數(shù)為:
    00000011 00000111 10000111 01000011
    00000001 00000001 00000001 00000001


00000011 00000111 10000111 01000011
00000111 10000111 01000011
10000111 01000011
01000011


注意下高8位已經(jīng)變成了四個(gè)分組之和了,所以最終得到的就是32位數(shù)中1的個(gè)數(shù)了

redis中的實(shí)現(xiàn)

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個(gè)字節(jié)匪凉,則使用variable-precision SWAR算法處理
    while(count>=28) {
        uint32_t aux1, aux2, aux3, aux4, aux5, aux6, aux7;
        //取出28個(gè)字節(jié)枪眉,每次取4個(gè),取7次
        aux1 = *p4++;
        aux2 = *p4++;
        aux3 = *p4++;
        aux4 = *p4++;
        aux5 = *p4++;
        aux6 = *p4++;
        aux7 = *p4++;
        count -= 28;
        //下面兩步計(jì)算四位分組1的個(gè)數(shù)
        aux1 = aux1 - ((aux1 >> 1) & 0x55555555);
        aux1 = (aux1 & 0x33333333) + ((aux1 >> 2) & 0x33333333);
        //下面兩步計(jì)算四位分組1的個(gè)數(shù)
        aux2 = aux2 - ((aux2 >> 1) & 0x55555555);
        aux2 = (aux2 & 0x33333333) + ((aux2 >> 2) & 0x33333333);
        ////下面兩步計(jì)算四位分組1的個(gè)數(shù)
        aux3 = aux3 - ((aux3 >> 1) & 0x55555555);
        aux3 = (aux3 & 0x33333333) + ((aux3 >> 2) & 0x33333333);
        ////下面兩步計(jì)算四位分組1的個(gè)數(shù)
        aux4 = aux4 - ((aux4 >> 1) & 0x55555555);
        aux4 = (aux4 & 0x33333333) + ((aux4 >> 2) & 0x33333333);
        ////下面兩步計(jì)算四位分組1的個(gè)數(shù)
        aux5 = aux5 - ((aux5 >> 1) & 0x55555555);
        aux5 = (aux5 & 0x33333333) + ((aux5 >> 2) & 0x33333333);
        //下面兩步計(jì)算四位分組1的個(gè)數(shù)
        aux6 = aux6 - ((aux6 >> 1) & 0x55555555);
        aux6 = (aux6 & 0x33333333) + ((aux6 >> 2) & 0x33333333);
        ////下面兩步計(jì)算四位分組1的個(gè)數(shù)
        aux7 = aux7 - ((aux7 >> 1) & 0x55555555);
        aux7 = (aux7 & 0x33333333) + ((aux7 >> 2) & 0x33333333);
        //計(jì)算8位分組1的個(gè)數(shù)再层,并使用乘法以及移位計(jì)算出總的1的個(gè)數(shù)
        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個(gè)字節(jié)的使用查表法計(jì)算出
    p = (unsigned char*)p4;
    while(count--) bits += bitsinbyte[*p++];
    return bits;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贸铜,一起剝皮案震驚了整個(gè)濱河市堡纬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蒿秦,老刑警劉巖烤镐,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異棍鳖,居然都是意外死亡炮叶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門渡处,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镜悉,“玉大人,你說我怎么就攤上這事医瘫÷乱蓿” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵醇份,是天一觀的道長稼锅。 經(jīng)常有香客問我,道長僚纷,這世上最難降的妖魔是什么矩距? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮怖竭,結(jié)果婚禮上剩晴,老公的妹妹穿的比我還像新娘。我一直安慰自己侵状,他們只是感情好赞弥,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著趣兄,像睡著了一般绽左。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上艇潭,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天拼窥,我揣著相機(jī)與錄音,去河邊找鬼蹋凝。 笑死鲁纠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鳍寂。 我是一名探鬼主播改含,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼迄汛!你這毒婦竟也來了捍壤?” 一聲冷哼從身側(cè)響起骤视,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹃觉,沒想到半個(gè)月后专酗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盗扇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年祷肯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疗隶。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡佑笋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抽减,到底是詐尸還是另有隱情允青,我是刑警寧澤橄碾,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布卵沉,位于F島的核電站,受9級(jí)特大地震影響法牲,放射性物質(zhì)發(fā)生泄漏史汗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一拒垃、第九天 我趴在偏房一處隱蔽的房頂上張望停撞。 院中可真熱鬧,春花似錦悼瓮、人聲如沸戈毒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽埋市。三九已至,卻和暖如春命贴,著一層夾襖步出監(jiān)牢的瞬間道宅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工胸蛛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留污茵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓葬项,卻偏偏與公主長得像泞当,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子民珍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • 1 關(guān)鍵字 1.1 關(guān)鍵字的概述 Java的關(guān)鍵字對(duì)java的編譯器有特殊的意義零蓉,他們用來表示一種數(shù)據(jù)類型笤受,或...
    哈哈哎呦喂閱讀 646評(píng)論 0 0
  • 背景 在java中float賦值給double,會(huì)產(chǎn)生精度問題敌蜂。 輸出為2.0999999046325684箩兽。 小...
    我叫小小強(qiáng)閱讀 19,209評(píng)論 2 23
  • 網(wǎng)站亂碼問題我們會(huì)經(jīng)常碰到,大多見于非英文的中文字符或其他字符亂碼章喉,而且汗贫,這類問題常常是因?yàn)榫幋a方式問題,主要原因...
    波段頂?shù)?/span>閱讀 2,842評(píng)論 1 9
  • Java 中位運(yùn)算符有與(&)秸脱、或(|)落包、非(~)、異或(^)摊唇、左移(<<)咐蝇、右移(>>)、無符號(hào)右移(>>>)巷查,...
    JohnnyShieh閱讀 1,093評(píng)論 0 0
  • 最近我的情緒和生活都進(jìn)入了低谷有序,我被我自以為是的愛情觀狠狠的打了臉。我開始正視自己的內(nèi)心真正的想法岛请,等我理順這一切...
    kingsley兮閱讀 256評(píng)論 0 0