Protocol Buffers

Protocol Buffers（プロトコルバッファー）は構造データのシリアライズを目的とした技術スタックである"Protocol buffers provide ... mechanism for serializing structured data" Google. Overview. Protocol Buffers. 2022-12-30閲覧.。

概要
通信や永続化において構造データのシリアライズは重要である。Protocol Buffers はこれを実現するための仕様およびライブラリであり、定義言語（インタフェース定義言語）・シリアライズ形式・各言語向けランタイムライブラリ・プロトコンパイラ生成コードの4要素からなる"Protocol buffers are a combination of the definition language ... , the code that the proto compiler generates to interface with data, language-specific runtime libraries, and the serialization format" Google. Overview. Protocol Buffers. 2022-12-30閲覧.。

Protocol Buffersのデザインの目的はシンプルさとパフォーマンスである。とりわけ、XMLより高速になるようデザインされている。GoogleはXMLとの比較で、3〜10倍小さく、20〜100倍高速であると主張しているDeveloper Guide - Protocol Buffers — Google Developers。Google自身が挙げている例では、XMLでは69バイト以上の物が Protocol Buffersでは28バイトであり、XMLのパースは5〜10マイクロ秒も必要だが、Protocol Buffersのパースは0.1〜0.2 マイクロ秒で完了するとしている。

Protocol Buffersは既にあるサービス向けへの明確なRPCスタックを含まない部分を除いてFacebookによるThriftプロトコルに非常に似ている。Protocol Buffersはオープンソースであり、RPCスタックはこれらのギャップを埋めるために生まれてきた。

データ構造およびサービスはプロトコル定義ファイル (Proto Definition file (.proto)) でデザインされ、protocプログラムによりコンパイルされる。このコンパイル作業により目的のサービスに適合するコードが生成される。例として、「example.proto」は「example.pb.cc」および「example.pb.h」を生成し、この中には「example.proto」で定義されたメッセージおよびサービスのためのC++クラスが定義されている。

Protocol Buffersによりあらゆる種類のフォーマットをシリアライズすることができるようになる。公式にサポートされた実装では完全なリフレクションインタフェースが利用可能であり、XMLやJSONと同じくらい容易にシリアライズ機構を整備することができる。

Protocol Buffersの主な目的は「ネットワーク上の通信を高速化すること」ではあるが、その自己記述性の低さに起因するシンプルさおよび高速さによりC++クラスおよび構造体を用いたデータ格納からの有力な置き換え手段としても用いられている。

現在、Googleにより開発されている。オリジナルのGoogle実装はC++、Java、Pythonによるものであり、自由ソフトウェアとしてオープンソースライセンスで公開されている。また、ActionScript・C言語・C#・Clojure・Common Lisp・D言語・Erlang・Go・Haskell・JavaScript・Lua・MATLAB・Mercury・Objective-C・OCaml・Perl・PHP・R言語・Ruby・Scala・.NET Frameworkなどの実装が利用可能であるThird-Party Add-ons for Protocol Buffers。

 仕様 

 インターフェース定義言語 
Protocol Buffers はインタフェース定義言語（IDL）としての側面をもつ。

 型 
protobuf IDLは型を定義する。各言語の型がprotobufの型へと変換されるため、インターフェースを挟んだ型システムの違いを意識せずに値を伝達できる。

スカラ型として15種の型（bool, string, bytes および以下の数値型）が定義される"Scalar Value Types    A scalar message field can have one of the following types" Google. Language Guide (proto3). Protocol Buffers. 2022-12-30閲覧.。
+表. 数値型固定長エンコーディング可変長エンコーディング32bit64bit32bit64bit浮動小数点数floatdouble整数fixed32fixed64int32int64符号なしuint32uint64符号付きsfixed32sfixed64sint32sint64
Protobufは効率性を重視しているため、シリアライズを意識したIDL型定義がなされている。これはスカラ数値型に表れており、エンコーディングの固定長/可変長が型の段階で表現されている（例: fixed32 と int32）。

 シリアライズ形式 
Protocol Buffers はシリアライズ形式としての側面をもつ。

 エンコーディング 
各フィールドごとに順番にエンコードされるが、各フィールドの先頭にはタグ番号と3ビットで表現された型の種類がつく。

int32などの整数値をエンコードした場合、必ず4バイト使うというわけではなく、0に近い数字はより少ないバイト数で表現される可変長数値表現を利用している。7ビット以下で表現できる整数は1バイトでエンコードされる。int32もint64もエンコード結果は同じ値になる。タグ番号＋3ビット型種別も同じく可変長数値表現でエンコードされる。ただしfixed32、fixed64などを使うと固定長でエンコードされる。またfloat、double などの浮動小数点数も固定長でエンコードされる。

文字列やバイト配列は、先頭に長さがつく形でエンコードされる。長さは上記の可変長数値表現を使っているため、長さの32ビット制限などはない。文字列はUTF-8で符号化する。

可変長数値表現は数値をリトルエンディアンで表現し7ビット単位で区切り、それぞれの最上位ビットにフラグを立てて8ビット単位のバイト列に変換する。最上位ビットは最終バイトだけ0として、それ以外は1を立てる。符号付き整数の場合はジグザグエンコーディングを使い、32ビットの場合は (n << 1) ^ (n >> 31) で正の数に変換してから可変長数値表現を使う。両方を併用した場合、-64〜63が1バイト、-8192〜8191が2バイトになり、32ビット整数は最長5バイトで表現される。

 例 
以下のように、polyline.protoファイルを作成する。

option java_package = "com.example";
option java_outer_classname = "PolyLineProtos";

// 点
message Point {
  required int32 x = 1;
  required int32 y = 2;
  optional string label = 3;
}

// 線分
message Line {
  required Point begin = 1;
  required Point end = 2;
  optional string label = 3;
}

// 多角形
message PolyLine {
  repeated Point point = 1;
  optional string label = 2;
}

Pointメッセージは2つの必須フィールドxとyを定義している。labelフィールドはオプションである。それぞれのフィールドはタグ番号を持っている。このタグ番号はイコール記号「=」の後で定義される。(例: xはタグ番号 1 を持つ)。タグ番号は1以上の自然数であり、タグ番号でフィールドを識別するため、重複なく必ず指定しないといけない。

LineおよびPolylineメッセージは Protocol Buffersによる入れ子メッセージの例であり、どちらもPointを用いている。PolyLineは繰り返しフィールドpointを持ち、ベクトルとして扱う。

メッセージのフィールドにはrequired、optional、repeatedのどれかが指定可能。また、値の型としては、各種プリミティブ型の整数や浮動小数点数、bool、string、bytesを指定可能。optionalはデフォルト値を指定可能。repeatedはJavaではjava.util.Listになる。

この後、これ protocによりコンパイルし、PolyLineProtos.javaやpolyline.pb.hなどが生成される。

Javaの場合は、以下のように利用することができる。

import com.example.PolyLineProtos.Point;
import com.example.PolyLineProtos.Line;
import com.example.PolyLineProtos.PolyLine;

Point begin = Point.newBuilder().setX(10).setY(20).build();
Point end = Point.newBuilder().setX(30).setY(40).build();
Line line1 = Line.newBuilder().setBegin(start).setEnd(end).setLabel("LINE1").build();
byte[] bytes = line1.toByteArray();
Line line2 = Line.parseFrom(bytes);

PolyLine.Builder polyLineBuilder = PolyLine.newBuilder();
for (int i = 0; i < 5; i++) {
    polyLineBuilder.addPoint(Point.newBuilder().setX(i).setY(i));
}
PolyLine polyLine = polyLineBuilder.build();

C++の場合は、以下のように利用することができる。

#include "polyline.pb.h"  // 'protoc polyline.proto'のコマンドラインからの呼び出しにより自動生成される

Line* createNewLine(const std::string& name) {
  Line* line = new Line;
  line->mutable_begin()->set_x(10);
  line->mutable_begin()->set_y(20);
  line->mutable_end()->set_x(30);
  line->mutable_end()->set_y(40);
  line->set_label(name);
  return line;
}

PolyLine* createNewPolyline() {
  PolyLine* polyline = new PolyLine;
  Point* point1 = polyline->add_point();
  point1->set_x(10);
  point1->set_y(10);
  Point* point2 = polyline->add_point();
  point2->set_x(10);
  point2->set_y(10);
  return polyline;
}

 利用 
Protocol BuffersはGoogleではデータの保存や構造化されたあらゆる種類の情報の交換で用いられている。Protocol Buffersサーバはカスタマイズされた遠隔手続き呼出し (RPC) に基づいており、Googleにおける全てのマシン間通信で実際に用いられている。

関連項目
 gRPC
 Abstract Syntax Notation One (ASN.1)
 JavaScript Object Notation (JSON)
 Extensible Markup Language (XML)

 参照 

 外部リンク 


Category:データシリアライゼーションフォーマット