Performant Entity Serialization: BSON vs MessagePack (vs JSON)
最近,我找到了MessagePack,这是Google协议缓冲区和JSON的另一种二进制序列化格式,它的性能也优于两者。
另外,MongoDB还使用了BSON序列化格式来存储数据。
有人可以详细说明BSON与MessagePack的区别和缺点吗?
只是为了完成高性能二进制序列化格式的列表:还有Gobs ,它将成为Google的协议缓冲区 strike>的后继产品。 但是与所有其他提到的格式相反,它们不是语言不可知的,而是依赖于Go的内置反射 strike>。此外,还有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(单击链接以查看图片):
我做了一个快速基准测试,比较了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 |
对于尺寸和性能很重要的受限环境(例如嵌入式),这有两个主要好处。