汎用シリアライズ方法(MessagePack/Protocol Buffers/FlatBuffers)
serializeMessagePackとは
JSONのように使うことができ、速くてサイズが小さい。
{"compact":true,"スキーマ":{"number": 999, "string": "aaa"}}
のjson(61bytes)をMessagePack(45bytes)に変換したのがこれ。見やすいように改行している。
82
a7 63 6f 6d 70 61 63 74 c3
ac e3 82 b9 e3 82 ad e3 83 bc e3 83 9e
82
a6 6e 75 6d 62 65 72 cd 03 e7
a6 73 74 72 69 6e 67 a3 61 61 61
一行目の82
は要素数2のfixmap(要素数15まで)を表す。
二行目のa7
が7バイトのfixstr(31bytesまで)で、
63 6f 6d 70 61 63 74
が"compact"。c3
はtrue。
三行目のac
は12バイトのfixstrで、e3 82 b9 e3 82 ad e3 83 bc e3 83 9e
が"スキーマ"。
四行目はこれのvalueで、要素数2のfixmap(82
)。
五行目は6バイトのfixstr(a6
)、“number”(6e 75 6d 62 65 72
)、
cd
がuint16で、999(03 e7
)。
六行目は6バイトのfixstr(a6
)、“string”(73 74 72 69 6e 67
)、3バイトのfixstr(a3
)、“aaa”(61 61 61
)。
と、こんな感じで分かりやすいバイナリになっている。文字列が多いとあまりサイズが変わらない。
使い方
$ go get github.com/ugorji/go/codec
mh := codec.MsgpackHandle{RawToString: true}
origin := serialize.Data{
Title: "aaaa",
}
func MessagePackEncode(mh codec.MsgpackHandle, origin Data) []byte {
var encoded []byte
enc := codec.NewEncoderBytes(&encoded, &mh)
if err := enc.Encode(&origin); err != nil {
panic(err)
}
return encoded
}
func MessagePackDecode(mh codec.MsgpackHandle, bytes []byte) Data {
var decoded Data
dec := codec.NewDecoderBytes(bytes, &mh)
if err := dec.Decode(&decoded); err != nil {
panic(err)
}
return decoded
}
Protocol Buffersとは
Googleのシリアライズライブラリ。gRPCでも使われる。
Googleが作ったRPCフレームワークgRPCを使ってみた
message typeをproto
ファイルで定義する(proto3)。このファイルから各言語のコードを生成することができる。
syntax = "proto3";
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
それぞれのmessage typeには名前と型を持つユニークな数値のフィールドが含まれる。一度割り当てた数値のtypeは変更しないようにする。 (Assigning Tags)
repeated
は0以上の任意の個数で、順番は保存される。
(Specifying Field Rules)
新しいコードがフィールドを追加してメッセージを送ると、古いコードは未知のフィールドを無視する。 逆に、古いコードがメッセージを送ると、 新しいコードの新しいフィールドにはデフォルト値が入る。 (Updating A Message Type)
使い方
protoファイル。
syntax = "proto3";
message Data {
string title = 1;
}
goのコードを生成。
$ brew install protobuf
$ protoc --version
libprotoc 3.1.0
$ go get github.com/golang/protobuf/protoc-gen-go
$ mkdir -p build/gen
$ protoc --proto_path=proto --go_out=data proto/data.proto
$ ls data
data.pb.go
生成されたコード。
// Code generated by protoc-gen-go.
// source: data.proto
// DO NOT EDIT!
/*
Package data is a generated protocol buffer package.
It is generated from these files:
data.proto
It has these top-level messages:
Data
*/
package data
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Data struct {
Title string `protobuf:"bytes,1,opt,name=title" json:"title,omitempty"`
}
func (m *Data) Reset() { *m = Data{} }
func (m *Data) String() string { return proto.CompactTextString(m) }
func (*Data) ProtoMessage() {}
func (*Data) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func init() {
proto.RegisterType((*Data)(nil), "Data")
}
func init() { proto.RegisterFile("data.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 67 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4a, 0x49, 0x2c, 0x49,
0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x92, 0xe1, 0x62, 0x71, 0x49, 0x2c, 0x49, 0x14, 0x12,
0xe1, 0x62, 0x2d, 0xc9, 0x2c, 0xc9, 0x49, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x70,
0x92, 0xd8, 0xc0, 0x8a, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xf5, 0x0d, 0xbe, 0x9b, 0x32,
0x00, 0x00, 0x00,
}
$ go get github.com/golang/protobuf/proto
func ProtoBufEncode(origin data.Data) []byte {
encoded, err := proto.Marshal(&origin)
if err != nil {
panic(err)
}
return encoded
}
func ProtoBufDecode(bytes []byte) data.Data {
var decoded data.Data
if err := proto.Unmarshal(bytes, &decoded); err != nil {
panic(err)
}
return decoded
}
FlatBuffers
Googleの、ゲームなどパフォーマンスを要求するアプリケーションのためのシリアライズライブラリ。 データにアクセスする前にparseやunpackする必要がなく、オブジェクトごとのメモリ割り当てが必要。
proto
の代わりにこんなschema
ファイルを書く。
namespace MyGame.Sample;
enum Color:byte { Red = 0, Green, Blue = 2 }
union Equipment { Weapon } // Optionally add more tables.
struct Vec3 {
x:float;
y:float;
z:float;
}
table Monster {
pos:Vec3; // Struct.
mana:short = 150;
hp:short = 100;
name:string;
friendly:bool = false (deprecated);
inventory:[ubyte]; // Vector of scalars.
color:Color = Blue; // Enum.
weapons:[Weapon]; // Vector of tables.
equipped:Equipment; // Union.
}
table Weapon {
name:string;
damage:short;
}
root_type Monster;
使い方
schemaファイル。
namespace dataf;
table Data {
title:string;
}
root_type Data;
goのコードを生成。
$ brew install flatbuffers
$ flatc --go fbs/dataf.fbs
$ ls dataf/
data.go
$ go get github.com/google/flatbuffers/go
生成されたコード。
// automatically generated by the FlatBuffers compiler, do not modify
package dataf
import (
flatbuffers "github.com/google/flatbuffers/go"
)
type data struct {
_tab flatbuffers.Table
}
func GetRootAsdata(buf []byte, offset flatbuffers.UOffsetT) *data {
n := flatbuffers.GetUOffsetT(buf[offset:])
x := &data{}
x.Init(buf, n+offset)
return x
}
func (rcv *data) Init(buf []byte, i flatbuffers.UOffsetT) {
rcv._tab.Bytes = buf
rcv._tab.Pos = i
}
func (rcv *data) Title() []byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(4))
if o != 0 {
return rcv._tab.ByteVector(o + rcv._tab.Pos)
}
return nil
}
func dataStart(builder *flatbuffers.Builder) {
builder.StartObject(1)
}
func dataAddTitle(builder *flatbuffers.Builder, title flatbuffers.UOffsetT) {
builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(title), 0)
}
func dataEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
return builder.EndObject()
}