谷物和 Boost 序列化是不是使用零拷贝?
Posted
技术标签:
【中文标题】谷物和 Boost 序列化是不是使用零拷贝?【英文标题】:Do cereal and Boost Serialization use zero-copy?谷物和 Boost 序列化是否使用零拷贝? 【发布时间】:2017-06-07 16:42:00 【问题描述】:我对几种序列化协议进行了一些性能比较,包括 FlatBuffers、Cap'n Proto、Boost 序列化和谷物。所有的测试都是用 C++ 编写的。
我知道 FlatBuffers 和 Cap'n Proto 使用零拷贝。使用零拷贝,序列化时间为空,但序列化对象的大小更大。
我认为谷物和 Boost 序列化没有使用零拷贝。但是,序列化时间(对于 int 和 double)几乎为零,并且序列化对象的大小几乎与 Cap'n Proto 或 Flatbuffers 相同。我在他们的文档中没有找到任何关于零拷贝的信息。
谷物和 Boost 序列化是否也使用零拷贝?
【问题讨论】:
“序列化时间为空”。这甚至意味着什么?能详细点吗? 【参考方案1】:Boost 和 Cereal不实现 Cap'n Proto 或 Flatbuffers 意义上的零拷贝。
使用真正的零拷贝序列化,实时内存对象的后备存储实际上与传递给read()
或write()
系统调用的内存段完全相同。根本没有打包/拆包步骤。
一般来说,这有很多含义:
未使用 new/delete 分配对象。构造消息时,首先分配消息,它为消息内容分配一个长的连续内存空间。然后,您直接在消息内部分配消息结构,接收实际上指向消息内存的指针。稍后写入消息时,单个write()
调用会将整个内存空间推向网络。
类似地,当您读入一条消息时,单个read()
调用(或者可能是 2-3 次)会将整条消息读入一个内存块。然后,您将获得一个指向消息“根”的指针(或类似指针的对象),您可以使用它来遍历它。请注意,在您的应用程序遍历消息之前,实际上不会检查消息的任何部分。
使用普通套接字,您的数据的唯一副本发生在 内核 空间中。使用 RDMA 网络,您甚至可以避免内核空间复制:数据从线路直接进入其最终内存位置。
在处理文件(而不是网络)时,可以直接从磁盘mmap()
一条非常大的消息并直接使用映射的内存区域。这样做是 O(1) - 文件有多大并不重要。当您实际访问文件的必要部分时,您的操作系统会自动对其进行分页。
同一台机器上的两个进程可以通过共享内存段进行通信,而无需复制。请注意,一般来说,常规的旧 C++ 对象在共享内存中不能很好地工作,因为内存段在两个内存空间中通常没有相同的地址,因此所有指针都是错误的。在零拷贝序列化框架中,指针通常表示为偏移量而不是绝对地址,因此它们与位置无关。
Boost 和 Cereal 不同:当您在这些系统中收到一条消息时,首先会对整个消息执行传递以“解包”内容。数据的最终存放位置是使用 new/delete 以传统方式分配的对象中。类似地,发送消息时,必须从这棵对象树中收集数据并将其打包到一个缓冲区中才能被写出。尽管 Boost 和 Cereal 是“可扩展的”,但真正实现零拷贝需要非常不同的底层设计;它不能作为扩展固定。
也就是说,不要假设零拷贝总是更快。 memcpy()
可以非常快,而您程序的其余部分可能会使成本相形见绌。同时,零拷贝系统往往有不方便的 API,特别是因为内存分配的限制。总体而言,使用传统的序列化系统可能会更好地利用您的时间。
零拷贝最明显有利的地方是在处理文件时,因为正如我提到的,您可以轻松地mmap()
一个巨大的文件,并且只读取其中的一部分。非零拷贝格式根本无法做到这一点。但是,在网络方面,优势就不太明显了,因为网络通信本身必然是 O(n)。
归根结底,如果您真的想知道哪种序列化系统最适合您的用例,您可能需要全部尝试并衡量它们。请注意,玩具基准通常具有误导性;你需要测试你的实际用例(或类似的东西)以获得有用的信息。
披露:我是 Cap'n Proto(零拷贝序列化器)和 Protocol Buffers v2(流行的非零拷贝序列化器)的作者。
【讨论】:
+1 以获得可靠且措辞恰当的答案。 [毫无价值的是,Boost 确实有一个用于对象、POD 和非 POD 的共享内存表示的库(使用自定义分配器和相对指针 (offset_ptr<>
))。]。【参考方案2】:
注意:我补充了另一个更好地理解问题的全部范围的答案
Boost 序列化是可扩展的。
它允许您的类型描述需要序列化的内容,以及描述格式的档案。
这可以是“零复制”——即唯一的缓冲是在接收数据的流中(例如套接字或文件描述符)。
有关 dynamic_bitset 序列化的有意识零拷贝实现的示例,请参阅此答案中的代码:How to serialize boost::dynamic_bitset?
我在网站上有很多这样的东西。另请查看BOOST_IS_BITWISE_SERIALIZABLE
的文档及其对容器序列化的影响(如果您序列化连续分配的按位序列化数据集合,则结果是零拷贝甚至__memcpy_sse4
等)。
旁注:Cap'n proto 完全做其他事情,AFAIK:它将一些对象编组为数据的期货。这显然是他们积极宣传的“快∞%,0µs!!!” (在从未检索到数据的情况下有点正确)。
【讨论】:
抱歉,我无法理解提升序列化如何实现零拷贝。可扩展是不够的:零拷贝需要一种非常不同的设计。存在一个名为“serialize()”的函数,它遍历类的所有内容,这在很大程度上不是零拷贝;该功能可能必须在序列化和解析时执行,它正在将字节复制到“存档”/从“存档”复制。 Cap'n Proto 没有这样的代码,因为在线结构已经被适当地安排,可以用作内存中的活动结构。 我提供了一个例子。我提到了文件缓冲。没有更多的了。 具体来说:使用 Cap'n Proto,我可以 mmap() 一条多千兆字节的消息,然后在 O(1) 时间内开始将其用作内存中的数据结构。内核只会在与我实际访问的结构部分相关的页面中分页;没有任何东西触及其他页面。这不仅仅是懒惰。 mmap 的区域实际上用作对象的实时后备存储。我不知道你怎么能用 boost 做到这一点。 问题显然是关于 Cap'n Proto、Flatbuffers 等定义的术语“零拷贝”。您似乎正在定义自己的新术语,称为“零拷贝”,但您的含义显然不同。因此,我认为您的回答在问题的背景下是不准确的。 Stack Overflow 不喜欢评论线程中发生的这类辩论,所以我将保留它。如果您想详细解释为什么不一样(是的,即使在流式传输时),请随时给我发送电子邮件或 Cap'n Proto 列表。 看。我明白你的意思。也许我误解了问题的范围因为我假设了流序列化(在我看来这是序列化(使东西序列化)的常规含义)。非常欢迎您的补充,我建议您发布答案。现在,我无法猜测我的答案,因为 OP 似乎已经接受了它。一旦你添加了你的,我们可以看看这是否为时过早。以上是关于谷物和 Boost 序列化是不是使用零拷贝?的主要内容,如果未能解决你的问题,请参考以下文章
boost 是不是支持 c++11 的 std::tuple 的序列化?