我在几个序列化协议之间进行了一些性能比较,包括FlatBuffers,Cap'n Proto,Boost序列化和谷物。所有测试都是用C ++编写的。
我知道FlatBuffers和Cap'n Proto使用零拷贝。使用零拷贝时,序列化时间为空,但序列化对象的大小更大。
我认为谷物和Boost序列化没有使用零拷贝。但是,序列化时间(对于int和double)几乎为空,序列化对象的大小几乎与Cap'n Proto或Flatbuffers对象相同。我没有在他们的文件中找到任何关于零拷贝的信息。
谷物和Boost序列化也使用零拷贝吗?
答案 0 :(得分:21)
Boost和Cereal在Cap&#Proto或Flatbuffers的意义上不实现零拷贝。
使用真正的零拷贝序列化,实时内存中对象的后备存储实际上与传递给read()
或write()
系统调用的内存段完全相同。根本没有包装/拆包步骤。
一般来说,这有很多含义:
write()
调用会将整个内存空间推到线路上。read()
调用(或可能是2-3)将整个消息读入一个内存块。然后你得到一个指针(或类似指针的对象)到" root"消息,您可以用它来遍历它。请注意,在应用程序遍历之前,实际上不会检查消息的任何部分。mmap()
发送一个非常大的消息,并直接使用映射的内存区域。这样做是O(1) - 文件的大小并不重要。当您实际访问文件时,您的操作系统将自动在文件的必要部分进行分页。Boost和Cereal是不同的:当你在这些系统中收到一条消息时,首先要对整个消息执行一次传递,然后解压缩"解压缩"内容。数据的最终静止位置是使用new / delete以传统方式分配的对象。类似地,当发送消息时,必须从该对象树收集数据并将其打包在一起形成一个缓冲区以便写出。尽管Boost和Cereal是可扩展的,但真正的零拷贝需要一个非常不同的底层设计;它不能作为扩展名用螺栓固定。
尽管如此,不要假设零拷贝总是会更快。 memcpy()
可能非常快,而你的其他程序可能会使成本相形见绌。同时,零拷贝系统往往具有不方便的API,特别是因为内存分配的限制。总的来说,可以更好地利用您的时间来使用传统的序列化系统。
零拷贝最明显有利的地方是操作文件时,因为正如我所提到的,你可以很容易地mmap()
一个巨大的文件,只读取它的一部分。非零复制格式根本无法做到这一点。然而,在网络方面,优势不太明显,因为网络通信本身必然是O(n)。
在一天结束时,如果您真的想知道哪个序列化系统最适合您的用例,您可能需要全部尝试并测量它们。请注意,玩具基准通常具有误导性;你需要测试你的实际用例(或类似的东西)以获得有用的信息。
披露:我是Cap&#cu; n Proto(零拷贝序列化器)和Protocol Buffers v2(一种流行的非零拷贝序列化器)的作者。
答案 1 :(得分:3)
Boost Serialization是可扩展的。
它允许您的类型描述需要序列化的内容,以及描述格式的档案。
这可以是“零拷贝” - 即唯一的缓冲是在接收数据的流中(例如套接字或文件描述符)。
有关dynamic_bitset序列化的有意识零拷贝实现的示例,请参阅此答案中的代码:How to serialize boost::dynamic_bitset?
我在网站上有很多这些。另请参阅BOOST_IS_BITWISE_SERIALIZABLE
的文档及其对容器序列化的影响(如果序列化连续分配的按位序列化数据集合,则结果为零拷贝甚至__memcpy_sse4
等)
旁注:Cap'n proto完全做了其他事情,AFAIK:它将一些对象作为期货到数据进行整理。这显然是他们积极宣传“∞%更快,0μs!!!” (在从未检索过数据的情况下,这有点真实。)