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

时间:2011-06-15 09:14:37

标签: serialization deserialization bson msgpack messagepack

最近,我发现了 MessagePack ,这是Google的Protocol Buffers和{{的另一种二进制序列化格式3}}也胜过两者。

此外,MongoDB还使用JSON序列化格式来存储数据。

有人可以详细说明差异以及BSON与MessagePack 的区别和优势吗?


只需完成高性能二进制序列化格式列表:还有BSON 将成为Google协议缓冲区的继承者。然而与所有其他提到的格式形成鲜明对比的是那些与语言无关且依赖Gobs 的格言库,至少还有其他语言的Gobs库。

6 个答案:

答案 0 :(得分:177)

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

格式设计

  1. 与JSON的兼容性

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

    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没有。

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

    实施和API设计

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

      MessagePack支持静态输入。

      与JSON或BSON一起使用的动态类型对于Ruby,Python或JavaScript等动态语言非常有用。但对于静态语言来说却很麻烦。你必须编写无聊的类型检查代码。

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

    2.     #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

        Protocol Buffers和Thrift需要IDL(不支持动态类型)并提供更成熟的IDL实现。

      2. MessagePack有流API(Ruby,Python,Java,C ++,...)

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

      3.     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
            }
        

答案 1 :(得分:16)

我知道这个问题在这一点上有点陈旧......我认为提到它取决于你的客户端/服务器环境是什么样的非常重要。

如果您在没有检查的情况下多次传递字节,例如使用消息队列系统或将日志条目传输到磁盘,那么您可能更喜欢使用二进制编码来强调紧凑的大小。否则,这是针对不同环境的个案问题。

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

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

就个人而言,我倾向于直接提供JSON提供的透明度,除非明确需要更轻的流量。通过带有gzip压缩数据的HTTP,网络开销的差异甚至可以解决格式之间的问题。

答案 2 :(得分:4)

快速测试显示缩小的JSON比二进制MessagePack更快地反序列化。在测试中,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  

答案 3 :(得分:0)

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

document    ::=     int32 e_list

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

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

答案 4 :(得分: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);
    }

答案 5 :(得分:0)

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

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

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

Huffman Tree of MessagePack