Google FlatBuffers使用教程

在服務端的開發過程當中,咱們常常須要完成 複雜數據結構 <–> 二進制數據 之間的序列化、反序列化操做。ios

與易於閱讀的Json相比,Google Protocol Buffers是一個不錯的選擇。然而,其速度依然比較慢。去年,Google又開源了推出了一款序列化利器:Google FlatBuffers。本文將簡介其用法,c++

一、爲何要用Google FlatBuffersgit

我就不用複雜的文字描述了,一份官方Benchmark數據就足以說明問題:github

Screenshot from 2015-01-22 14:43:24能夠看到,與Protocol Buffers相比,儘管FlatBuffers在空間使用上不具備優點,可是反序列化上的性能很是彪悍!json

爲何這麼高效呢,援引官方的文檔:數組

  • 對序列化數據的訪問不須要打包和拆包——它將序列化數據存儲在緩存中,這些數據既能夠存儲在文件中,又能夠經過網絡原樣傳輸,而沒有任何解析開銷;(這是最主要的緣由,ProtoBuffer、JSON等均須要拆包和解包)緩存

  • 內存效率和速度——訪問數據時的惟一內存需求就是緩衝區,不須要額外的內存分配。 這裏可查看詳細的基準測試;網絡

  • 擴展性、靈活性——它支持的可選字段意味着不只能得到很好的前向/後向兼容性(對於長生命週期的遊戲來講尤爲重要,由於不須要每一個新版本都更新全部數據);數據結構

  • 最小代碼依賴——僅僅須要自動生成的少許代碼和一個單一的頭文件依賴,很容易集成到現有系統中。再次,看基準部分細節;函數

  • 強類型設計——儘量使錯誤出如今編譯期,而不是等到運行期才手動檢查和修正;

  • 使用簡單——生成的C++代碼提供了簡單的訪問和構造接口;並且若是須要,經過一個可選功能能夠用來在運行時高效解析Schema和類JSON格式的文本;

  • 跨平臺——支持C++十一、Java,而不須要任何依賴庫;在最新的gcc、clang、vs2010等編譯器上工做良好;

二、編譯&安裝

能夠在github上找到最新的release版本:https://github.com/google/flatbuffers/releases

wget https://github.com/google/flatbuffers/archive/v1.0.3.zip unzip ./v1.0.3.zip cd flatbuffers-1.0.3/ cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/coder4/soft/flatbuffers -G "Unix Makefiles" make && make install

1

2

3

4

5

wget https://github.com/google/flatbuffers/archive/v1.0.3.zip

unzip ./v1.0.3.zip

cd flatbuffers-1.0.3/

cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/coder4/soft/flatbuffers -G "Unix Makefiles"

make && make install

編譯完畢的庫和include就在中了。

和protobuffer相似,咱們本身開發時候並不須要連接其餘lib,只要include和生成的代碼就能夠了。

最有用的是bin/flatc,這個是編譯schema、生成代碼的程序。

三、編寫自定義Schema

只有schema肯定,才能保證序列化、反序列化的高性能(由於會生成裸代碼,比json等動態執行的要高效不少)

咱們構造一個以下的Schema文件 test.fb

namespace TestApp; struct KV { key: ulong; value: double; } table TestObj { id:ulong; name:string; flag:ubyte = 0; list:[ulong]; kv:KV; } root_type TestObj;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

namespace TestApp;

 

struct KV {

key: ulong;

value: double;

}

 

table TestObj {

id:ulong;

name:string;

flag:ubyte = 0;

list:[ulong];

kv:KV;

}

 

root_type TestObj;

簡單解釋一下,FlatBuffer,支持的數據結構有:基本類型和複雜類型。

基本類型: 

  • 8 bit: byte ubyte bool

  • 16 bit: short ushort

  • 32 bit: int uint float

  • 64 bit: long ulong double

複雜類型:

  • 數組 (用中括號表示 [type]). 不支持嵌套數組,能夠用table實現

  • 字符串 string, 支持 UTF-8 或者 7-bit ASCII. 對於其餘編碼能夠用數組 [byte]或者[ubyte]表示。

  • Struct 只支持基本類型或者嵌套Struct

  • Table 相似Struct,可是能夠支持任何類型。

看完這些,你們應該就很清楚上面的fb是怎麼生成的啦。

KV是一個Struct,有2個名爲key和value的變量。

TestObj是一個Table,包含了KV的成員、list數組、flag的uint8(初始值0)、以及uint64的id。

最後定義了根入口是TestObj,這句必定要有,不然沒法反序列化。

四、編譯Schema

執行:

./bin/flatc -c -b ./test.fb

1

./bin/flatc -c -b ./test.fb

會生成一個.h文件:

test_generated.h

五、序列化、反序列化

#include "test_generated.h" #include <vector> #include <iostream> using namespace std; using namespace TestApp; int main() { flatbuffers::FlatBufferBuilder builder; /////////// Serialize ////////// // Create list std::vector<uint64_t> vec; for(size_t i=0;i<10;i++) { vec.push_back(i); } // Create flat buffer inner type auto id = 123; auto name = builder.CreateString("name"); auto list = builder.CreateVector(vec); // vector auto flag = 1; auto kv = KV(1, 1.0); // struct // table auto mloc = CreateTestObj(builder, id, name, flag, list, &kv); builder.Finish(mloc); char* ptr = (char*)builder.GetBufferPointer(); uint64_t size = builder.GetSize(); ////////// Deserialize ////////// auto obj = GetTestObj((uint8_t*)ptr); cout << obj->id() << endl; cout << obj->name()->c_str() << endl; cout << obj->flag() << endl; for(size_t i=0;i<obj->list()->size();i++) { cout << obj->list()->Get(i) << endl; } // can use assign to std::vector for speed up // vec.reserve(obj->list()->size()); // vec.assign(obj->list()->begin(), obj->list()->end()); cout << obj->kv()->key() << endl; cout << obj->kv()->value() << endl; }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

#include "test_generated.h"

#include <vector>

#include <iostream>

 

using namespace std;

using namespace TestApp;

 

int main()

{

    flatbuffers::FlatBufferBuilder builder;

 

    /////////// Serialize //////////

    // Create list

    std::vector<uint64_t> vec;

    for(size_t i=0;i<10;i++)

    {

        vec.push_back(i);

    }

    // Create flat buffer inner type

    auto id = 123;

    auto name = builder.CreateString("name");

    auto list = builder.CreateVector(vec); // vector

    auto flag = 1;

    auto kv = KV(1, 1.0); // struct

    // table

    auto mloc = CreateTestObj(builder, id, name, flag, list, &kv);

    builder.Finish(mloc);

 

    char* ptr = (char*)builder.GetBufferPointer();

    uint64_t size = builder.GetSize();

    

    ////////// Deserialize //////////

    auto obj = GetTestObj((uint8_t*)ptr);

 

    cout << obj->id() << endl;

    cout << obj->name()->c_str() << endl;

    cout << obj->flag() << endl;

    for(size_t i=0;i<obj->list()->size();i++)

    {

        cout << obj->list()->Get(i) << endl;

    }

 

    // can use assign to std::vector for speed up

    // vec.reserve(obj->list()->size());

    // vec.assign(obj->list()->begin(), obj->list()->end());

 

    cout << obj->kv()->key() << endl;

    cout << obj->kv()->value() << endl;

 

}

因爲FlatBuffers使用了c++0x的特性,因此編譯必須使用支持c++0x的版本,例如

g++ -std=c++0x ./test.cpp -I ./include/

1

g++ -std=c++0x ./test.cpp -I ./include/

對代碼說明以下:

  1. 因爲FlatBuffer中的類型小複雜,且官方也沒有給出明確的例子,因此我就偷懶用了auto特性。處女座請自行參閱源代碼。

  2. 基礎類型直接賦值,符合類型須要用FlatBufferBuilder.CreateXXX,例如String和Vector

  3. Struct類型,直接構造

  4. Table類型,用CreateXXX,其中XXX爲定義的類型,這個在生成代碼的.h中

  5. 序列化時候,能夠直接從Builder取出指針和length,而後就能夠塞入string啦~

  6. 反序列化的時候,注意全部成員都須要用函數()而不是直接使用成員名。

小結一下,生成的代碼還真是小亂,用法五花八門,爲了性能就忍忍吧,用習慣就行了。

最後說一句,FlatBuffer還支持其餘更爲高級的用法,例如直接反序列化爲Json/從Json序列化,可是性能比較慢,你們慢慢探索吧。

相關文章
相關標籤/搜索