担当の長屋です。
日々、業務中にいいなと思ったことを書いてみます。
今回は汎用的で且つ軽量高速なバイナリベースのシリアライズ形式である「MessagePack」について書いてみようと思います。JSON-likeに汎用的に扱え、バイナリ形式で且つ余分なデータを切り詰めているためシリアライズ後のサイズはJSONよりもかなり小さくなります。
MessagePack
MessagePack: It's like JSON. but fast and small.
実際にMessagePack形式でシリアライズしたデータのサイズとJSONの場合のサイズを比べてみたいと思います。
#include <iostream> #include <msgpack.hpp> using namespace std; void test01() { //シリアライズ用テストデータ map<string,int> tests = {{"AAAA",100},{"BBBB",121},{"CCCC",50},{"DDDD",25}}; //テストデータと同じ構造のJSON文字列 const char * jsonStr = "{\"AAAA\":100,\"BBBB\":121,\"CCCC\":50,\"DDDD\":25}"; //シリアライズ用のバッファ msgpack::sbuffer buff; //シリアライズ msgpack::pack(&buff, tests); //シリアライズされたデータ。 //調整されたバイナリデータになっておりかなり軽量 char * data = buff.data(); //msgpackとjsonでサイズを比べてみる cout << "MsgPack size" << strlen(data) << endl; cout << "json size" << strlen(jsonStr) << endl; //シリアライズされたデータをデシリアライズしてみる msgpack::object_handle oh = msgpack::unpack(data, buff.size()); msgpack::object obj = oh.get(); map<string,int> deserializeDatas; obj.convert(deserializeDatas); //デシリアライズしたデータの出力 for (auto data : deserializeDatas) cout << data.first << ":" << data.second << endl; }
出力結果
MsgPack size25 json size43 AAAA:100 BBBB:121 CCCC:50 DDDD:25
長さベースのサイズ比較ですが、JSONに比べて約60%近くにまでサイズがカットされています。
次は自作のデータオブジェクトをそのままシリアライズ・デシリアライズしてみます。
C++の場合自作のデータオブジェクトや列挙型でも提供されているマクロを用いることで簡単にシリアライズとデシリアライズができるようになっています。
#include <iostream> #include <msgpack.hpp> using namespace std; //テスト列挙型 enum class TestType : int { Unknown, A = 100, B = 200, C = 300 }; //シリアライズしたい列挙型を設定 MSGPACK_ADD_ENUM(TestType); //テストオブジェクトその1 struct TestData { string name; int id; float value; TestType type; //シリアライズしたい変数を設定 MSGPACK_DEFINE(name,id,value,type); }; //テストオブジェクトその2 struct TestDataGroup { int groupId; vector<TestData> testDatas; //シリアライズしたい変数を設定 MSGPACK_DEFINE(groupId,testDatas); }; void test02() { //テストデータグループAを作成 TestDataGroup groupA; groupA.groupId = 10; groupA.testDatas.push_back({"AAAA",1,123.456f,TestType::A}); groupA.testDatas.push_back({"BBBB",2,180.554f,TestType::B}); groupA.testDatas.push_back({"CrashFever!",3,10.0f,TestType::C}); //テストデータグループBを作成 TestDataGroup groupB; groupB.groupId = 20; groupB.testDatas.push_back({"CCCC",1,100.0f,TestType::A}); groupB.testDatas.push_back({"DDDD",2,110.0f,TestType::B}); groupB.testDatas.push_back({"EEEE",3,120.0f,TestType::C}); //シリアライズ用のオブジェクトを作成 map<string,TestDataGroup> testDataGroups; testDataGroups["GroupA"] = groupA; testDataGroups["GroupB"] = groupB; //シリアライズ msgpack::sbuffer sbuf; msgpack::pack(sbuf, testDataGroups); //デシリアライズ msgpack::object_handle oh = msgpack::unpack(sbuf.data(), sbuf.size()); msgpack::object data = oh.get(); map<string,TestDataGroup> deserializeDatas; data.convert(deserializeDatas); //デシリアライズした物を出力してみる for (const auto & group : deserializeDatas) { cout << "key = " << group.first << " GroupID = "<< group.second.groupId << endl; for (const auto & data : group.second.testDatas) { cout << data.name << " " << data.id << " " << data.value << " " << (int)data.type << endl; } cout << endl; } }
出力結果
key = GroupA GroupID = 10 AAAA 1 123.456 100 BBBB 2 180.554 200 CrashFever! 3 10 300 key = GroupB GroupID = 20 CCCC 1 100 100 DDDD 2 110 200 EEEE 3 120 300
このように自作のオブジェクトであっても簡単にシリアライズ・デシリアライズができます。また各オブジェクト用にシリアライザなどを作る必要がないのも大きな利点です。
ただ、シリアライズされたデータはバイナリ形式になるため人の目で見ることができないので使い分けが必要な部分は出てくるとおもいます。
弊社の「CrashFever」の場合はサーバーとのやり取りはJSONで行いマルチプレイ時の速度が必要な場面はMessagePackを使用しています。