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


137

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

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

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


只是为了完成高性能二进制序列化格式的列表:还有Gob ,它们将成为Google的Protocol Buffers的后继产品。但是,与所有其他提到的格式相反,这些格式不是语言不可知的,并且依赖于Go的内置反射,此外还有Gobs库,至少可以使用除Go之外的其他语言。


3
似乎大多像是一堆营销炒作。[“ compiled”]序列化格式的性能取决于所使用的实现。虽然某些格式本来会产生更多开销(例如,因为JSON是动态处理的),但格式本身并没有“速度”。然后页面继续“选择并选择”它如何进行自我比较……这是一种非常公正的方式。不适合我。

6
更正:采空区无意取代协议缓冲区,可能永远也不会。另外,Gob与语言无关(可以用任何一种语言进行读取/编写,请参见code.google.com/p/libgob),但是它们的定义与Go处理数据的方式紧密匹配,因此它们与Go一起使用效果最佳。
凯尔C

6
与msgpack性能基准的链接已损坏(msgpack.org/index/speedtest.png)。
Aliaksei Ramanau 2012年

Answers:


197

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

格式设计

  1. 与JSON的兼容性

    尽管其名称,BSON与JSON的兼容性与MessagePack相比并不太好。

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

    MessagePack旨在从JSON透明地转换为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,协议缓冲区,节流和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
    }

33
MessagePack与Google Protobufs在数据大小方面(因此在空中性能方面)相比如何?
Ellis

4
第一点掩盖了MessagePack具有无法在JSON中表示的原始字节功能的事实。因此,在这方面它与BSON一样……

4
@lttlrck通常,除非另有说明并在通道的两侧都同意,否则假定原始字节为字符串(通常为utf-8)。msgpack用作流/序列化格式... json ..较少冗长,但人类可读性也较低。
Tracker1 2012年

4
“ MessagePack具有类型检查API。BSON没有。” 并不完全准确。对于使用静态类型语言的BSON实现,实际上也是如此。
布兰登·布莱克

1
MessagePack现在具有BINARY数据类型,因此与JSON进行1-1反序列化兼容性的参数不再完全成立。
zimbatm 2015年

16

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

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

某些环境与msgpack / protobuf之间的序列化和反序列化速度非常快,而其他环境则没有。通常,语言/环境的级别越低,二进制序列化越好。在高级语言(node.js,.Net,JVM)中,您通常会看到JSON序列化实际上更快。那么问题就变成,您的网络开销是比您的内存/ cpu受到更多或更少的约束吗?

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

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


6
本地MsgPack 在按协议大小使用ProtocolBuffer 时才有效,因为键的长度(始终存在的文本)很短,例如“ a”或“ b”- 否则是整个有效负载的不重要部分。它们在ProtocolBuffer中总是很短,它使用IDL /编译将字段描述符映射到id。这也是什么使MsgPack“动态”,这ProtocolBuffers肯定不是..
user2864740

2
终点虽然很好:但是gzip / deflate在处理键的冗余方面确实非常好,以防这种键“较长而又重复很多”(MsgPack,JSON / BSON和XML等在许多记录上),但无济于事这里所有的协议都是缓冲区。Avro通过单独传输模式来手动消除密钥冗余。
user2864740

4

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

MessagePack:

//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
└── msgpack@0.1.7  

7
绝对取决于实现。我使用Python 2.7.3进行的测试将一个489K test.json(相当于409K等效的test.msgpack)解压缩,结果表明,对于10,000次迭代,simplejson2.6.2耗时66.7秒,而msgpack0.2.2仅需28.8 秒。
2012年

2
该Article.json来自何处?
Ant6n 2014年

伙计们,测试代码在我上面的评论中,您还期望什么,Article.json是我们项目中的json序列化对象。而到现在
为止,

14
这是不公平的性能比较,因为JS具有在C ++中本地实现的JSON,而JS中具有msgpack。
Alex Panchenko '16

2
您正在尝试使MessagePack讲拉丁语比说罗马语更好。JSON是JavaScript的本机(C ++),而MessagePack是用JavaScript编写的(可解释)。基本上,这是比较两个代码段,一个是用JavaScript编写的,另一个是用C ++编写的。
Ramazan Polat

0

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

document    ::=     int32 e_list

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

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

0

我做了一个快速基准测试,比较了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

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

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

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

消息包的霍夫曼树

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.