Ruby
ProtocolBuffers

ProtocolBuffersについて調べてみた

More than 3 years have passed since last update.


Protocol Buffersとは

Google謹製のデータの通信・永続化のためのシリアライズフォーマット

公式サイト

Github

Wikipedia


シリアライズ?

(合っているか怪しい)

プロセスAのデータをプロセスBに送信したいとする。

このときまず、プロセスAのデータをファイル保存できたりネットワーク転送できる形式に変換しなければいけない。すなわちメモリ上に散在しているであろうデータを連続するメモリ領域に配置換えする必要があり、この操作のことをシリアライズ(データの直列化)という。

また、もしデータがプロセスAの環境や言語に依存する方法でシリアライズされた場合、環境や言語が異なるであろうプロセスBはデータを受け取ってもデシリアライズできなくて読み取れず「???」となってしまうため、シリアライズには非言語依存・非環境依存という性質が望まれる。

一般に広く使われているシリアライズ方式には大きく分けてテキスト形式とバイナリ形式の2つがある。


  • テキスト形式の例(JSON)

decoded_data = JSON.parse("{\"id\":100,\"name\":\"aiueo4u\"}") # decode

=> {"id"=>100, "name"=>"aiueo4u"}
JSON.generate(decoded_data) # encode
=> "{\"id\":100,\"name\":\"aiueo4u\"}"


  • バイナリ形式の例(Protocol Buffers)

decoded_data = Person.decode("\bd\x12\aaiueo4u") # decode

=> <Person: id: 100, name: "aiueo4u">
Person.encode(decoded_data) # encode
=> "\bd\x12\aaiueo4u"


JSONとかと比べてどうなの?

JSON等のテキスト形式に比べて使い勝手は落ちるがパフォーマンスが良い


  • メリット


    • データサイズが小さい

    • エンコード、デコードが速くて省メモリ



  • デメリット


    • エンコードされたデータが人間には読みにくい or 読めない

    • 事前にスキーマを定義するファイル(.proto)を用意する必要があり一手間かかる



cf. 各種シリアライザ比較ベンチマーク by Google


簡単な使い方



  1. .protoファイルを作成


    • 以下の様なファイルにてデータ構造を事前に定義しておく。


    person.proto

    syntax = 'proto3';

    message Person {
    int32 id = 1;
    string name = 2;
    }





  2. 1で作成した .proto ファイルをコンパイル

    $ protoc person.proto --ruby_out=./
    


    • 以下のファイルが生成される。


    person.rb

    # Generated by the protocol buffer compiler.  DO NOT EDIT!
    
    # source: person.proto

    require 'google/protobuf'

    Google::Protobuf::DescriptorPool.generated_pool.build do
    add_message "Person" do
    optional :id, :int32, 1
    optional :name, :string, 2
    end
    end

    Person = Google::Protobuf::DescriptorPool.generated_pool.lookup("Person").msgclass





  3. 2で生成されたファイルを用いる


    test.rb

    require './person.rb'

    person = Person.new(id: 100, name: 'aiueo4u')
    p person.id # => 100
    p person.name # => 'aiueo4u'

    person.name = 'hogehoge'
    p person.name # => 'hogehoge'

    encoded_data = Person.encode(person) # encode

    # encoded_data = "\bd\x12\bhogehoge"
    # Now you can send encoded_data to remote or write to a file.

    person2 = Person.decode(encoded_data) # decode
    p person2.id # => 100
    p person2.name # => 'hogehoge'

    person2.to_json # => "{\"id\":100,\"name\":\"hogehoge\"}"





インストール方法 for Ruby


  1. コンパイラ(protoc)のインストール

$ brew install --devel protobuf

※RubyにはProtocol Buffers v3から対応しているので --devel を付けている。付けないとv2が入ってしまう(2015/12/04 時点)

2. Gemのインストール

gem install --prerelease google-protobuf

※(2015/12/04 時点で)Rubyバインディングはαバージョンの提供になっているため --prerelease が必要


参考


  • バイナリ形式



    • Flatbuffers


      • Google謹製のゲーム向け、爆速・省メモリ

      • デコード不要でバイナリデータに直接アクセスする


        • 実質、デコード時間・メモリ消費が無い



      • Protocol Buffers同様に事前定義ファイルを用意する必要あり

      • Protocol Buffersと比べ、データサイズがちょっと大きい(バイナリデータを直接読み取るための情報が含まれているため


      • Facebookとかが使ってる




    • MessagePack


      • JSONのバイナリ版(速い、小さい)

      • たくさんの言語で使える。

      • だが、スキーマ定義ができない模様

      • redis, fluentd, Pinterestとかで使われている




    • BSON


      • JSONのバイナリ版

      • バイナリデータも格納出来る模様

      • スキーマ定義あり

      • だがMessagePackにサイズ・性能ともに劣る模様


        • データに配列が含まれているとJSONよりもデータサイズが大きくなる(!?)模様




      • MongoDBとかで使われている





エンコーディング仕様の解説