Varint Byte Encoding對比Group Varint Encoding報告

一委可、背景及概要

??序列化,是指將數(shù)據(jù)以一種特定的格式轉化為方便計算機經(jīng)網(wǎng)絡傳輸或儲存的格式腊嗡。
??反序列化着倾,以便在另一臺計算機或者本機上以序列化格式重新得到數(shù)據(jù)的操作。
例如:
?? uint32_t number = 10;
??在計算機中實際儲存為32位數(shù)據(jù):(高)00000000 00000000 00000000 00001010(低)
??為4個Byte數(shù)據(jù)燕少,因此可以將4個Bytes放入char類型(同樣為8bits)的容器string當中卡者,以完成序列化。再將string中的數(shù)據(jù)取出解釋為uint32_t完成反序列化客们。
??基于此思想崇决,Jeff Dean在Building Software Systems at Google andLessons Learned[1]中提出了VBC編碼以高效地完成uint32_t的序列化與反序列化。本實驗完成了VBC編碼的基本實現(xiàn)和測試速度底挫。
??但由于VBC編碼的decode方式必須忍受分支預測的開銷恒傻,因此Jeff Dean提出了一種新的編碼方式Group Varint Encoding,來解決分支預測的問題建邓。

二盈厘、實驗方案

實驗設備

實驗設備

實驗概述

1、VBC編碼

??本實驗先將按照如下思路進行了C++程序的編寫并完成單元測試


encode 單個uint32_t
string encode_number(uint32_t n){
    char current_byte;
    string byte;
    while (1) {
        current_byte = n & 0x7f;
        byte = current_byte + byte;
        if (n < 128) break;
        n = n >> 7;
    }
    byte[byte.size()-1] += 128;
    return byte;
}
將vector<uint32_t>中所有uint32_t序列化為一個string
string encode(const vector<uint32_t>& in_sequence){
    string serialized_integers;
    const int size = in_sequence.size();
    for (int i = 0; i < size; i++) {
        serialized_integers += encode_number(in_sequence[i]);
    }
    return serialized_integers;
}
反序列化涝缝,將string中數(shù)據(jù)decode添加到vector<uint32_t>
vector<uint32_t> decode(const string& serialized_integers){
    vector<uint32_t> numbers;
    uint32_t current_number = 0;
    int index = 0;
      while (index < serialized_integers.length()) {
        unsigned char current_byte = serialized_integers[index];
        uint32_t part = (uint32_t)current_byte;
        //對于每一個字節(jié)都要判斷當前字節(jié)是否為uint32_t的最后一個字節(jié)扑庞,分支預測開銷大
        if (part < 128) {
            current_number = (current_number << 7) + part;
        }else {
            current_number = (current_number << 7) + (part - 128);
            numbers.push_back(current_number);
            current_number = 0;
        }
        index++;
    }
    return numbers; 
}
代碼分析

對于encode部分和decode部分譬重,variant byte coding的方法均在每一個Byte上判斷是否屬于是當前整數(shù)的最后一個字節(jié),因此在分支預測方面的開銷大罐氨,每一次分支預測的需要5ns左右的時間臀规,因此提出以下方法Group Variant Encoding。

2栅隐、Group Variant Encoding編碼

編碼思想:將每一個uint32_t用2個bits來表示該整數(shù)需要幾個Byte來表示,一個uint32_t為4個字節(jié)正好需要兩個bits來表示塔嬉。
二進制 ????需要的字節(jié)數(shù)
00???????1
00???????2
00???????3
00???????4
一個uint32_t正好需要四個狀態(tài)來表示

2019-11-22 22-56-56 的屏幕截圖.png
encode 一組uint32_t(數(shù)量<=4)
string encode_single_group(const vector<uint32_t>& integers){   
    string encoded; 
    //該組的tag    
    char tags = 0;
    size_t offset = 6;
    //對于每一個數(shù)據(jù)進行encode
    for(int i = 0; i < integers.size(); i++){
        uint32_t number = integers[i];
        //每個數(shù)據(jù)有一個子標簽count記錄需要多少個Byte
        char count = 0x0;
        //為0則不需要取,直接添加至encoded
        if(number != 0){
            char current_byte = 0xFF;
            //當還有字節(jié)可以編碼時:取出字節(jié)-》計數(shù)增加-》加入編碼
            while(number != 0){
                current_byte = current_byte & number;
                count++;
                encoded += current_byte;
                number = number >> 8;
                current_byte = 0xFF;    
            }
            tags = tags | (count-1 << offset);
        }else{
            encoded += (char)0x00;
            tags = tags | (count << offset);    
        }
        offset -= 2;
    }
    return (tags + encoded);
}
encode接口:將vector<uint32_t>中所有uint32_t序列化為string
string group_varint_encode(const vector<uint32_t> &original_integers){
    if(original_integers.empty()) return "";
    string encoded;
    //分組器
    int group_count = 0;
    vector<uint32_t> group;
    for(int i = 0; i < original_integers.size(); i++){
        group_count++;
        group.push_back(original_integers[i]);
        //若能填滿四個數(shù)據(jù)則編碼
        if((group_count & 3) == 0){
            encoded += encode_single_group(group);
            group_count = 0;
            group.clear();
        }
    }
    //若數(shù)據(jù)數(shù)量不為4的倍數(shù)則再單獨編碼
    if(group_count != 0) encoded += encode_single_group(group);
    return encoded;
}
decode接口:反序列化string為vector<uint32_t>
vector<uint32_t> group_varint_decode(const string& encoded_byte_stream){
    if(encoded_byte_stream.empty()) return {};
    vector<uint32_t> decoded;   
    const int len = encoded_byte_stream.length();
    int index = 0;
    while(index < len{
        //取當前tag
        char tags = encoded_byte_stream[index++];
        int offset = 6;
        //對當前組數(shù)據(jù)進行decode(tag后的5~17個Bytes)
        //這里對每個字節(jié)有條件判斷
        while(offset >= 0 && index < len){
            //follow為組內的其中一個數(shù)據(jù)需要的字節(jié)數(shù)
            int follow = ((tags >> offset) & 3) + 1;
            uint32_t value = 0;
            //向后連續(xù)取follow字節(jié)
            for(int i = 0; i < follow; i++){
                unsigned char* trans = (unsigned char*)&encoded_byte_stream[index++];
                value = value | ((uint32_t)*trans << i*8);
            }
            decoded.push_back(value);
            offset -= 2;
        }       
    }
    return decoded;
}
代碼分析

在該實現(xiàn)當中注意decode的以下部分

        int offset = 6;
        //對當前組數(shù)據(jù)進行decode(tag后的5~17個Bytes)
        //這里對每個字節(jié)有條件判斷
        while(offset >= 0 && index < len)

如果這么編寫代碼租悄,造成的后果是谨究,并沒有達到Group Varint Encoding的核心目的-------減小分支預測的開銷,為了驗證該設想泣棋,運用perf分析工具來進行觀察胶哲。
#######觀察

#perf record生成一份性能報告
#-e + 性能指標可以對某一具體指標進行測量
#-g 生成具體到每個函數(shù)的性能
#最后是可執(zhí)行文件
#perf report 打開性能測試報告
perf record -e branch-misses -g ./a.out
perf report
2019-11-21 13-01-11 的屏幕截圖.png

可以看到甚至在此實現(xiàn)中,GVE的decode方法的branch-miss的占比甚至比VBC方法還高出了5%,因此需要對上述循環(huán)進行改進潭辈。

改進思路

改進思路無非在于鸯屿,在運用tags時每取2bits都要判斷tags是否結束,即是否該組數(shù)據(jù)處理完成把敢。
那么可以換一種處理方法寄摆,在處理前每一次都判斷該tags是否是最后一組tags。
如果對于四個一組的數(shù)據(jù)修赞,順序的連續(xù)取四次婶恼,不需要判斷。
而對于最后一組數(shù)據(jù)數(shù)量在1 <= number <= 4,則需要每取一次判斷是否越界柏副。

性能預測

如果處理的數(shù)據(jù)足夠大勾邦,那么分支預測的開銷相較于之前,減小了3/4割择。

改進的decode接口
uint32_t get_value(const string& encoded_byte_stream, const vector<uint32_t>& masks, size_t current_tag, uint32_t* address, int& index){
    uint32_t value = 0; 
    address = (uint32_t*)&encoded_byte_stream[index];
    value = *address & masks[current_tag];
    index += current_tag + 1;
    return value;

}

vector<uint32_t> group_varint_decode(const string& encoded_byte_stream){
    if(encoded_byte_stream.empty()) return {};
    vector<uint32_t> decoded;   
    const int len = encoded_byte_stream.length();
    int index = 0;
    const vector<uint32_t> masks = {0xff, 0xffff, 0xffffff, 0xffffffff};
    while(index < len){
        //取當前tag
        char tags = encoded_byte_stream[index++];
        int number = (tags & 3) + ((tags >> 2) & 3) + ((tags >> 4) & 3) + ((tags >> 6) & 3) + 4;
        uint32_t* address = (uint32_t*)&encoded_byte_stream[index];
        size_t current_tag = (tags >> 6) & 3; 
        //最后一個      
        if((index + number) >= len){
            decoded.push_back(get_value(encoded_byte_stream,masks, current_tag, address, index));
            if(index >= len) break;
            
            current_tag = (tags >> 4) & 3;
            decoded.push_back(get_value(encoded_byte_stream,masks, current_tag, address, index));
            if(index >= len) break;

            current_tag = (tags >> 2) & 3;
            decoded.push_back(get_value(encoded_byte_stream,masks, current_tag, address, index));
            if(index >= len) break;
        
            current_tag = tags & 3;
            decoded.push_back(get_value(encoded_byte_stream,masks, current_tag, address, index));
            if(index >= len) break;

        }else{
                    
            decoded.push_back(get_value(encoded_byte_stream,masks, current_tag, address, index));

            current_tag = (tags >> 4) & 3;
            decoded.push_back(get_value(encoded_byte_stream,masks, current_tag, address, index));

            current_tag = (tags >> 2) & 3;
            decoded.push_back(get_value(encoded_byte_stream,masks, current_tag, address, index));

            current_tag = tags & 3;
            decoded.push_back(get_value(encoded_byte_stream,masks, current_tag, address, index));
        }
    }       
    return decoded;
}

三检痰、性能對比

encode對比

encode性能對比圖

圖像分析:
為兩組對照,首先锨推,可以看到,無論是否開啟編譯優(yōu)化O3公壤,GVE方法編碼速度始終高于VBC編碼换可。
其次,若開啟編譯優(yōu)化O3厦幅,GVE方法的性能得到大幅度提升沾鳄,提升了將近4倍,而VBC編碼的速度也提升了將近7倍确憨,但還是低于未進行編譯優(yōu)化的GVE編碼译荞。

結論:GVE的encode方法在速度上明顯優(yōu)于VBC的encode方法瓤的。

decode對比

decode性能對比圖

圖像分析:
從上到下依次為(圖例起名可能引起混淆):

VBC 未編譯優(yōu)化
GVE 未編譯優(yōu)化 未改進
VBC O3編譯優(yōu)化
GVE O3編譯優(yōu)化 未改進
GVE 未編譯優(yōu)化 改進后
GVE O3編譯優(yōu)化 改進后

? ?首先,無論對于GVE/VBC(未編譯優(yōu)化)還是 GVE/VBC(O3優(yōu)化),兩種方法性能其實相差不遠吞歼,均遭受了分支預測所帶來的開銷圈膏,表現(xiàn)為曲線貼合緊密。
? ?其次篙骡,關注標出數(shù)據(jù)的兩條稽坤,黃色和綠色, 表示,在進行改進之后糯俗,GVE的性能得到了大幅度的提升尿褪,綠色的改進后GVE(O3優(yōu)化)的速度遠遠超過其他版本的曲線,帶來了性能上的優(yōu)勢得湘。

結論:改進編寫方式后的GVE的decode體現(xiàn)了Jeff Dean提出GVE編碼方法的初衷杖玲,即避免遭遇分支預測所帶來的性能瓶頸。

四淘正、Conclusion And Future Work

? ?本次實驗運用兩種序列化uint32_t的方法VBC和GVE, 進行了初步的性能測試摆马,以及體會了編碼方式的不同可以在性能上得到了巨大的提升,以及初步使用了perf性能分析工具和數(shù)據(jù)可視化的方法來解決問題跪帝。
? ?future work方面今膊,可以看到在最后一張decode性能對比圖中,當數(shù)據(jù)數(shù)量上升時伞剑,GVE的decode方法遭遇了性能的嚴重下滑斑唬,因此,需要找出為什么會出現(xiàn)這樣的斷層現(xiàn)象黎泣,以及如果數(shù)據(jù)進一步增大恕刘,該方法性能的體現(xiàn)。找到性能瓶頸并嘗試解決該現(xiàn)象抒倚,達到既高效又穩(wěn)定的目標褐着。

引用:

[1]:https://static.googleusercontent.com/media/research.google.com/zh-CN//people/jeff/Stanford-DL-Nov-2010.pdf

[2]:http://www.ir.uwaterloo.ca/book/addenda-06-index-compression.html

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市托呕,隨后出現(xiàn)的幾起案子含蓉,更是在濱河造成了極大的恐慌,老刑警劉巖项郊,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馅扣,死亡現(xiàn)場離奇詭異,居然都是意外死亡着降,警方通過查閱死者的電腦和手機差油,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來任洞,“玉大人蓄喇,你說我怎么就攤上這事发侵。” “怎么了妆偏?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵刃鳄,是天一觀的道長。 經(jīng)常有香客問我楼眷,道長铲汪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任罐柳,我火速辦了婚禮掌腰,結果婚禮上,老公的妹妹穿的比我還像新娘张吉。我一直安慰自己齿梁,他們只是感情好,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布肮蛹。 她就那樣靜靜地躺著勺择,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伦忠。 梳的紋絲不亂的頭發(fā)上省核,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音昆码,去河邊找鬼气忠。 笑死,一個胖子當著我的面吹牛赋咽,可吹牛的內容都是我干的旧噪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼脓匿,長吁一口氣:“原來是場噩夢啊……” “哼淘钟!你這毒婦竟也來了?” 一聲冷哼從身側響起陪毡,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤米母,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后毡琉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爱咬,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年绊起,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片燎斩。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡虱歪,死狀恐怖蜂绎,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情笋鄙,我是刑警寧澤师枣,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站萧落,受9級特大地震影響践美,放射性物質發(fā)生泄漏。R本人自食惡果不足惜找岖,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一陨倡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧许布,春花似錦兴革、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至袁余,卻和暖如春擎勘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背颖榜。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工棚饵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人朱转。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓蟹地,卻偏偏與公主長得像,于是被迫代替她去往敵國和親藤为。 傳聞我的和親對象是個殘疾皇子怪与,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內容