ProtocolBuffersについて調べてみた

# Protocol Buffersとは

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

[公式サイト](https://developers.google.com/protocol-buffers/)
[Github](https://github.com/google/protobuf)
[Wikipedia](https://ja.wikipedia.org/wiki/Protocol_Buffers)


# シリアライズ？

（合っているか怪しい）

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

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

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

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

- テキスト形式の例（JSON）

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

- バイナリ形式の例（Protocol Buffers）

```rb
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](https://google.github.io/flatbuffers/md__benchmarks.html)

# 簡単な使い方

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](https://google.github.io/flatbuffers/)
        - Google謹製のゲーム向け、爆速・省メモリ
        - デコード不要でバイナリデータに直接アクセスする
            - 実質、デコード時間・メモリ消費が無い
        - Protocol Buffers同様に事前定義ファイルを用意する必要あり
        - Protocol Buffersと比べ、データサイズがちょっと大きい（バイナリデータを直接読み取るための情報が含まれているため
        - [Facebook](https://code.facebook.com/posts/872547912839369/improving-facebook-s-performance-on-android-with-flatbuffers/)とかが使ってる
    - [MessagePack](http://msgpack.org/index.html)
        - JSONのバイナリ版（速い、小さい）
        - たくさんの言語で使える。
        - だが、スキーマ定義ができない模様
        - redis, fluentd, Pinterestとかで使われている
    - [BSON](http://bsonspec.org/)
        - JSONのバイナリ版
        - バイナリデータも格納出来る模様
        - スキーマ定義あり
        - だが[MessagePackにサイズ・性能ともに劣る](http://d.hatena.ne.jp/tullio/20121103/1351965832)模様
            - データに配列が含まれているとJSONよりもデータサイズが大きくなる(!?)模様
        - [MongoDB](https://www.mongodb.com/json-and-bson)とかで使われている

[エンコーディング仕様の解説](http://qiita.com/aiueo4u/items/38195248a29e9ff719c7)