关于反序列化:性能实体序列化:BSON与MessagePack(对比JSON)

Performant Entity Serialization: BSON vs MessagePack (vs JSON)

最近,我找到了MessagePack,这是Google协议缓冲区和JSON的另一种二进制序列化格式,它的性能也优于两者。

另外,MongoDB还使用了BSON序列化格式来存储数据。

有人可以详细说明BSON与MessagePack的区别和缺点吗?

只是为了完成高性能二进制序列化格式的列表:还有Gobs ,它将成为Google的协议缓冲区的后继产品。 但是与所有其他提到的格式相反,它们不是语言不可知的,而是依赖于Go的内置反射。此外,还有Gobs库,至少针对Go以外的其他语言。


//请注意,我是MessagePack的作者。这个答案可能有偏见。

格式设计

  • 与JSON的兼容性

    尽管名称如此,但BSON与JSON的兼容性与MessagePack相比并不是很好。

    BSON具有特殊类型,例如" ObjectId"," Min key"," UUID"或" MD5"(我认为MongoDB需要这些类型)。这些类型与JSON不兼容。这意味着当您将对象从BSON转换为JSON时,某些类型信息可能会丢失,但是当然只有当这些特殊类型在BSON源中时,这些信息才会丢失。在单个服务中同时使用JSON和BSON可能是不利的。

    MessagePack旨在透明地从JSON转换为JSON。

  • MessagePack小于BSON

    MessagePack的格式不如BSON冗长。结果,MessagePack可以序列化小于BSON的对象。

    例如,一个简单的映射{" a":1," b":2}使用MessagePack序列化为7个字节,而BSON使用19个字节。

  • BSON支持就地更新

    使用BSON,您可以修改存储的对象的一部分,而无需重新序列化整个对象。假设映射{{a":1," b":2}存储在文件中,并且您想将" a"的值从1更新为2000。

    对于MessagePack,1仅使用1个字节,而2000使用3个字节。因此," b"必须向后移2个字节,而" b"没有被修改。

    使用BSON,1和2000都使用5个字节。由于这种冗长,您不必移动" b"。

  • MessagePack具有RPC

    MessagePack,协议缓冲区,节流和Avro支持RPC。但是BSON没有。

  • 这些差异意味着MessagePack最初是为网络通信而设计的,而BSON是为存储而设计的。

    实施和API设计

  • MessagePack具有类型检查API(Java,C ++和D)

    MessagePack支持静态键入。

    与JSON或BSON一起使用的动态类型对于动态语言(例如Ruby,Python或JavaScript)很有用。但是麻烦的是静态语言。您必须编写无聊的类型检查代码。

    MessagePack提供了类型检查API。它将动态类型的对象转换为静态类型的对象。这是一个简单的示例(C ++):

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
        #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>();
        }
  • MessagePack具有IDL

    它与类型检查API有关,MessagePack支持IDL。 (可以从以下网址获得规范:http://wiki.msgpack.org/display/MSGPACK/Design+of+IDL)

    协议缓冲区和节流协议需要IDL(不支持动态类型),并提供更成熟的IDL实现。

  • MessagePack具有流API(Ruby,Python,Java,C ++等)

    MessagePack支持流式反序列化器。此功能对于网络通信很有用。这是一个示例(Ruby):

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        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
        }


    我知道这个问题在这一点上有点过时了……我认为提到这取决于您的客户机/服务器环境是非常重要的。

    如果您不检查就多次传递字节,例如使用消息队列系统或将日志条目流传输到磁盘,那么您可能更喜欢二进制编码来强调紧凑的大小。否则,在不同的环境下会因情况而异。

    某些环境可以在msgpack / protobuf中进行非常快速的序列化和反序列化,而其他环境则没有。通常,语言/环境的级别越低,二进制序列化越好。在高级语言(node.js,.Net,JVM)中,您通常会看到JSON序列化实际上更快。然后,问题就变成了与您的内存/ CPU相比,您的网络开销受到的限制更多还是更少?

    关于msgpack vs bson vs协议缓冲区... msgpack是该组中最小的字节,协议缓冲区大致相同。 BSON比其他两种定义了更广泛的本机类型,并且可能与您的对象模式更好地匹配,但这使它更加冗长。协议缓冲区具有被设计为流式传输的优点……这使其成为二进制传输/存储格式的更自然的格式。

    就个人而言,除非明确需要减少流量,否则我将倾向于JSON直接提供的透明度。通过带有压缩数据的HTTP,格式之间的网络开销差异甚至不再是问题。


    快速测试显示,与二进制MessagePack相比,缩小的JSON反序列化的速度更快。在测试中,Article.json是550kb缩小的JSON,Article.mpack是420kb的MP版本。当然可能是实施问题。

    MessagePack:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 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);
    }

    所以时间是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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

    这样可以节省空间,但是速度更快?没有。

    测试版本:

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


    好吧,正如作者所说,MessagePack最初是为网络通信而设计的,而BSON是为存储而设计的。

    MessagePack紧凑,而BSON冗长。
    MessagePack旨在节省空间,而BSON专为CURD(省时)而设计。

    最重要的是,MessagePack的类型系统(前缀)遵循霍夫曼编码,在这里我画了一个霍夫曼树的MessagePack(单击链接以查看图片):

    Huffman Tree of MessagePack


    我做了一个快速基准测试,比较了MessagePack和BSON的编码和解码速度。至少在具有大型二进制数组的情况下,BSON更快:

    1
    2
    3
    4
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
        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);
        }

    尚未提及的关键区别在于BSON包含整个文档以及其他嵌套子文档的大小信息(以字节为单位)。

    1
    document    ::=     int32 e_list

    对于尺寸和性能很重要的受限环境(例如嵌入式),这有两个主要好处。

  • 您可以立即检查要解析的数据是否代表完整的文档,或者是否需要在某个时候(从某个连接或存储中)请求更多数据。由于这很可能是异步操作,因此您可能在解析之前已经发送了新请求。
  • 您的数据可能包含带有不相关信息的整个子文档。 BSON使您可以通过使用子文档的大小信息跳过该文档,轻松遍历子文档中的下一个对象。另一方面,msgpack包含所谓的映射(类似于BSON的子文档)中的元素数量。尽管这无疑是有用的信息,但对解析器没有帮助。您仍然必须解析地图中的每个对象,而不能只是跳过它。根据数据的结构,这可能会对性能产生巨大影响。