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

最近我发现了 MessagePack ,一种可替代谷歌的 协议缓冲JSON二进制序列化格式,它的性能也超过了这两种格式。

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

有人能详细说说 BSON 与 MessagePack 的区别和缺点/优点吗?


仅仅为了完成性能二进制序列化格式的列表: 还有 < strong > Gobs 它们将成为谷歌协议缓冲的继承者。然而,除了 Go 之外,至少还有其他语言的 与上面提到的所有格式相比,这些格式并不是语言无关的,而是依赖于 Go 的内置反射 Gobs 库。

74284 次浏览

//请注意,我是 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

    消息包、协议缓冲、节俭和 Avro 支持 RPC,但是 BSON 不支持。

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

实现和 API 设计

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

    MessagePack 支持静态类型。

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

    MessagePack 提供类型检查 API。它将动态类型的对象转换为静态类型的对象:

    #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

    协议缓冲和节俭需要 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
}

快速测试显示缩小的 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);
}

杰森:

// 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

我认为有一点非常重要,那就是它取决于您的客户机/服务器环境是什么样子的。

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

一些环境可以对 msgpack/Protobuf 进行非常快速的序列化和反序列化,而另一些环境就没有这么快了。一般来说,语言/环境级别越低,二进制序列化就越好。在高级语言(node.js,。Net,JVM) ,您将经常看到 JSON 序列化实际上更快。那么问题就变成了网络开销是否比内存/CPU 更受限制?

至于 msgpack 对 bson 对协议缓冲... msgpack 是组中最小的字节,协议缓冲大致相同。BSON 比其他两个定义了更广泛的本机类型,并且可能更适合您的对象模型,但是这使得它更加详细。协议缓冲的优势在于它被设计成流媒体... 这使得它成为一种更自然的二进制传输/存储格式。

就个人而言,我倾向于直接使用 JSON 提供的透明性,除非明确需要更轻量级的流量。通过使用 gzip 数据的 HTTP,网络开销的差异在格式之间甚至更小。

一个尚未提及的关键区别是,BSON 包含整个文档和进一步嵌套子文档的大小信息(以字节为单位)。

document    ::=     int32 e_list

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

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

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

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

使用 C # Newton. Json 和 MessagePack 作者: neecc:

    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);
}

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

MessagePack 是紧凑的,而 BSON 是冗长的。 MessagePack 是为空间效率而设计的,而 BSON 是为 CURD (时间效率)而设计的。

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

Huffman Tree of MessagePack