web-dev-qa-db-ja.com

パフォーマンスの高いエンティティのシリアル化:BSON vs MessagePack(vs JSON)

最近見つけたMessagePack、代替 Googleの Protocol Buffers および JSON へのbinaryシリアル化形式は、どちらも優れています。

また、データを保存するためにMongoDBで使用される BSON シリアル化形式もあります。

BSONとMessagePackの違いと欠点/利点を詳しく説明できますか?


高性能なバイナリシリアル化形式のリストを完成させるためだけに、次もあります。 Gobs googleのProtocol Buffersの後継になる。しかしながら 言及されている他のすべての形式とは対照的に、これらは言語に依存せず、 Goの組み込みリフレクション に依存しています 少なくともGo以外の言語用のGobsライブラリもあります。

130
Alex

//私はMessagePackの作成者であることに注意してください。この答えには偏りがあるかもしれません。

フォーマット設計

  1. JSONとの互換性

    その名前にもかかわらず、BSONのJSONとの互換性は、MessagePackと比較してそれほど良くありません。

    BSONには、「ObjectId」、「Min key」、「UUID」、「MD5」などの特別なタイプがあります(これらのタイプはMongoDBで必要だと思います)。これらのタイプはJSONと互換性がありません。つまり、オブジェクトをBSONからJSONに変換すると、一部のタイプ情報が失われる可能性がありますが、もちろんこれらの特別なタイプがBSONソースにある場合のみです。単一のサービスでJSONとBSONの両方を使用することは不利な場合があります。

    MessagePackは、JSONとの間で透過的に変換されるように設計されています。

  2. MessagePackはBSONよりも小さい

    MessagePackの形式は、BSONより冗長ではありません。その結果、MessagePackはBSONよりも小さいオブジェクトをシリアル化できます。

    たとえば、単純なマップ{"a":1、 "b":2}はMessagePackで7バイトにシリアル化されますが、BSONは19バイトを使用します。

  3. BSONはインプレース更新をサポートします

    BSONを使用すると、オブジェクト全体を再シリアル化することなく、保存されているオブジェクトの一部を変更できます。マップ{"a":1、 "b":2}がファイルに保存されていて、 "a"の値を1から2000に更新するとします。

    MessagePackでは、1は1バイトのみを使用しますが、2000は3バイトを使用します。したがって、「b」は2バイトだけ後方に移動する必要がありますが、「b」は変更されません。

    BSONでは、1と2000の両方が5バイトを使用します。この冗長性のため、「b」を移動する必要はありません。

  4. MessagePackにはRPCがあります

    MessagePack、Protocol Buffers、Thrift、AvroはRPCをサポートしています。しかし、BSONはそうではありません。

これらの違いは、MessagePackが元々ネットワーク通信用に設計されているのに対し、BSONはストレージ用に設計されていることを意味します。

実装とAPI設計

  1. MessagePackには型チェックAPI(Java、C++、およびD)があります

    MessagePackは静的型付けをサポートしています。

    JSONまたはBSONで使用される動的型指定は、Ruby、Python、またはJavaScriptなどの動的言語に役立ちます。しかし、静的言語にとっては面倒です。退屈なタイプチェックコードを記述する必要があります。

    MessagePackは、型チェックAPIを提供します。動的に型付けされたオブジェクトを静的に型付けされたオブジェクトに変換します。以下に簡単な例を示します(C++):

    #include <msgpack.hpp>

    class myclass {
    private:
        std::string str;
        std::vector<int> vec;
    public:
        // This macro enables this class to be serialized/deserialized
        MSGPACK_DEFINE(str, vec);
    };

    int main(void) {
        // serialize
        myclass m1 = ...;

        msgpack::sbuffer buffer;
        msgpack::pack(&buffer, m1);

        // deserialize
        msgpack::unpacked result;
        msgpack::unpack(&result, buffer.data(), buffer.size());

        // you get dynamically-typed object
        msgpack::object obj = result.get();

        // convert it to statically-typed object
        myclass m2 = obj.as<myclass>();
    }
  1. MessagePackにはIDLがあります

    これは型チェックAPIに関連しており、MessagePackはIDLをサポートしています。 (仕様は以下から入手できます: http://wiki.msgpack.org/display/MSGPACK/Design+of+IDL

    プロトコルバッファとスリフトはIDLを必要とし(ダイナミックタイピングをサポートしない)、より成熟したIDL実装を提供します。

  2. MessagePackにはストリーミングAPI(Ruby、Python、Java、C++など)があります

    MessagePackは、ストリーミングデシリアライザーをサポートしています。この機能は、ネットワーク通信に役立ちます。次に例を示します(Ruby):

    require 'msgpack'

    # write objects to stdout
    $stdout.write [1,2,3].to_msgpack
    $stdout.write [1,2,3].to_msgpack

    # read objects from stdin using streaming deserializer
    unpacker = MessagePack::Unpacker.new($stdin)
    # use iterator
    unpacker.each {|obj|
      p obj
    }
181

私はこの質問がこの時点で少し時代遅れであることを知っています...それはあなたのクライアント/サーバー環境がどのように見えるかに依存することに言及することは非常に重要だと思います。

メッセージキューシステムやディスクへのログエントリのストリーミングなど、検査せずにバイトを複数回渡す場合は、コンパクトサイズを強調するためにバイナリエンコーディングを使用することをお勧めします。それ以外の場合は、異なる環境でのケースバイケースの問題です。

環境によっては、msgpack/protobufとの間で非常に高速なシリアル化と逆シリアル化が可能な場合とそうでない場合があります。一般的に、言語/環境が低レベルであるほど、バイナリシリアル化が機能します。高レベルの言語(node.js、.Net、JVM)では、JSONシリアル化が実際に高速であることがよくわかります。問題は、メモリ/ CPUよりもネットワークオーバーヘッドが多少制約されているかどうかです。

Msgpack vs bson vs protocol buffersに関して... msgpackはグループの最小バイトで、プロトコルバッファはほぼ同じです。 BSONは、他の2つよりも幅広いネイティブタイプを定義し、オブジェクトモードにより適している場合がありますが、これにより、より冗長になります。プロトコルバッファには、ストリーミングするように設計されているという利点があります。これにより、バイナリ転送/ストレージ形式のより自然な形式になります。

個人的には、より軽いトラフィックが明確に必要でない限り、JSONが直接提供する透明性に傾倒します。 gzip圧縮されたデータを使用したHTTPを介した場合、ネットワークオーバーヘッドの違いは、フォーマット間で問題がさらに少なくなります。

16
Tracker1

クイックテストは、縮小されたJSONがバイナリMessagePackよりも高速にデシリアライズされることを示しています。テストでは、Article.jsonは550kbの縮小JSONであり、Article.mpackは420kb MPバージョンです。もちろん実装の問題かもしれません。

メッセージパック:

//test_mp.js
var msg = require('msgpack');
var fs = require('fs');

var article = fs.readFileSync('Article.mpack');

for (var i = 0; i < 10000; i++) {
    msg.unpack(article);    
}

JSON:

// test_json.js
var msg = require('msgpack');
var fs = require('fs');

var article = fs.readFileSync('Article.json', 'utf-8');

for (var i = 0; i < 10000; i++) {
    JSON.parse(article);
}

時代は次のとおりです。

Anarki:Downloads oleksii$ time node test_mp.js 

real    2m45.042s
user    2m44.662s
sys     0m2.034s

Anarki:Downloads oleksii$ time node test_json.js 

real    2m15.497s
user    2m15.458s
sys     0m0.824s

スペースは節約できますが、高速ですか?番号。

テスト済みのバージョン:

Anarki:Downloads oleksii$ node --version
v0.8.12
Anarki:Downloads oleksii$ npm list msgpack
/Users/oleksii
└── [email protected]  
4

著者が言ったように、MessagePackはもともとネットワーク通信用に設計されており、BSONはストレージ用に設計されています。

MessagePackはコンパクトですが、BSONは冗長です。 MessagePackはスペース効率がよく、BSONはCURD(時間効率)向けに設計されています。

最も重要なことは、MessagePackの型システム(プレフィックス)がHuffmanエンコーディングに従うことです。ここでは、MessagePackのHuffmanツリーを描画しました(リンクをクリックして画像を表示します)。

Huffman Tree of MessagePack

0
Jim

MessagePackとBSONのエンコードとデコードの速度を比較するための簡単なベンチマークを作成しました。 BSONは、少なくとも大きなバイナリ配列がある場合は高速です。

BSON writer: 2296 ms (243487 bytes)
BSON reader: 435 ms
MESSAGEPACK writer: 5472 ms (243510 bytes)
MESSAGEPACK reader: 1364 ms

NeueccによるC#Newtonsoft.JsonおよびMessagePackの使用:

    public class TestData
    {
        public byte[] buffer;
        public bool foobar;
        public int x, y, w, h;
    }

    static void Main(string[] args)
    {
        try
        {
            int loop = 10000;

            var buffer = new TestData();
            TestData data2;
            byte[] data = null;
            int val = 0, val2 = 0, val3 = 0;

            buffer.buffer = new byte[243432];

            var sw = new Stopwatch();

            sw.Start();
            for (int i = 0; i < loop; i++)
            {
                data = SerializeBson(buffer);
                val2 = data.Length;
            }

            var rc1 = sw.ElapsedMilliseconds;

            sw.Restart();
            for (int i = 0; i < loop; i++)
            {
                data2 = DeserializeBson(data);
                val += data2.buffer[0];
            }
            var rc2 = sw.ElapsedMilliseconds;

            sw.Restart();
            for (int i = 0; i < loop; i++)
            {
                data = SerializeMP(buffer);
                val3 = data.Length;
                val += data[0];
            }

            var rc3 = sw.ElapsedMilliseconds;

            sw.Restart();
            for (int i = 0; i < loop; i++)
            {
                data2 = DeserializeMP(data);
                val += data2.buffer[0];
            }
            var rc4 = sw.ElapsedMilliseconds;

            Console.WriteLine("Results:", val);
            Console.WriteLine("BSON writer: {0} ms ({1} bytes)", rc1, val2);
            Console.WriteLine("BSON reader: {0} ms", rc2);
            Console.WriteLine("MESSAGEPACK writer: {0} ms ({1} bytes)", rc3, val3);
            Console.WriteLine("MESSAGEPACK reader: {0} ms", rc4);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }

        Console.ReadLine();
    }

    static private byte[] SerializeBson(TestData data)
    {
        var ms = new MemoryStream();

        using (var writer = new Newtonsoft.Json.Bson.BsonWriter(ms))
        {
            var s = new Newtonsoft.Json.JsonSerializer();
            s.Serialize(writer, data);
            return ms.ToArray();
        }
    }

    static private TestData DeserializeBson(byte[] data)
    {
        var ms = new MemoryStream(data);

        using (var reader = new Newtonsoft.Json.Bson.BsonReader(ms))
        {
            var s = new Newtonsoft.Json.JsonSerializer();
            return s.Deserialize<TestData>(reader);
        }
    }

    static private byte[] SerializeMP(TestData data)
    {
        return MessagePackSerializer.Typeless.Serialize(data);
    }

    static private TestData DeserializeMP(byte[] data)
    {
        return (TestData)MessagePackSerializer.Typeless.Deserialize(data);
    }
0
itix

まだ言及されていない重要な違いは、BSONにはドキュメント全体およびさらにネストされたサブドキュメントのサイズ情報がバイト単位で含まれていることです。

document    ::=     int32 e_list

これには、サイズとパフォーマンスが重要な制限された環境(組み込みなど)に2つの大きな利点があります。

  1. 解析しようとしているデータが完全なドキュメントを表しているのか、ある時点で(接続やストレージから)さらに要求する必要があるのか​​をすぐに確認できます。これはおそらく非同期操作であるため、解析する前にすでに新しいリクエストを送信している可能性があります。
  2. データには、無関係な情報を含むサブドキュメント全体が含まれている場合があります。 BSONを使用すると、サブドキュメントのサイズ情報を使用してサブドキュメントをスキップすることで、サブドキュメントを通過した次のオブジェクトに簡単に移動できます。一方、msgpackには、マップと呼ばれるもの内の要素の数が含まれています(BSONのサブドキュメントに似ています)。これは間違いなく有用な情報ですが、パーサーには役立ちません。マップ内のすべてのオブジェクトを解析する必要があり、単にスキップすることはできません。データの構造によっては、これがパフォーマンスに大きな影響を与える可能性があります。
0
Vinci