cereal 和 Boost Serialization 是否使用零拷贝?

Do cereal and Boost Serialization use zero-copy?

我对几个序列化协议做了一些性能比较,包括 FlatBuffers、Cap'n Proto、Boost 序列化和 cereal。所有测试都是用 C++ 编写的。

我知道 FlatBuffers 和 Cap'n Proto 使用零拷贝。使用零拷贝,序列化时间为空,但序列化对象的大小更大。

我以为cereal 和Boost 序列化没有使用零拷贝。但是,序列化时间(对于 int 和 double)几乎为零,并且序列化对象的大小几乎与 Cap'n Proto 或 Flatbuffers 的相同。我在他们的文档中没有找到任何关于零拷贝的信息。

谷物和 Boost 序列化是否也使用零拷贝?

Note: I bountied the other answer which understood the full scope of the question better

Boost 序列化是可扩展的。

它允许您的类型描述需要序列化的内容,并允许档案描述格式。

这可以是 "zero-copy" - 即唯一的缓冲是在接收数据的流中(例如套接字或文件描述符)。

有关有意识地 zero-copy 实现 dynamic_bitset 序列化的示例,请参阅此答案中的代码:

我在网站上有很多。另请查看 BOOST_IS_BITWISE_SERIALIZABLE 的文档及其对容器序列化的影响(如果序列化连续分配的 bitwise-serializable 数据集合,结果是 zero-copy 甚至 __memcpy_sse4 等)。

Side-note: Cap'n proto does something else entirely, AFAIK: it marshals some objects as futures-to-the-data. This is apparently what they advertise aggressively as "∞% faster, 0µs!!!" (which is somewhat true in the case where the data is never retrieved).

Boost 和 Cereal 在 Cap'n Proto 或 Flatbuffers 的意义上实现 zero-copy。

使用真正的 zero-copy 序列化,实时 in-memory 对象的后备存储实际上与传递给 read()write() 的内存段完全相同系统调用。根本没有 packing/unpacking 步骤。

一般来说,这有很多含义:

  • 未使用 new/delete 分配对象。构造消息时,首先分配消息,它为消息内容分配了一个长的连续内存space。然后,您直接在消息 中分配消息结构,接收实际上指向消息内存的指针。稍后写入消息时,单个 write() 调用将整个内存 space 推到线路上。
  • 类似地,当您读入一条消息时,单个 read() 调用(或可能 2-3 次)会将整条消息读入一个内存块。然后,您将获得一个指向消息 "root" 的指针(或 pointer-like 对象),您可以使用它来遍历它。请注意,在您的应用程序遍历之前,不会实际检查消息的任何部分。
  • 对于普通套接字,数据的唯一副本发生在 内核 space 中。使用 RDMA 网络,您甚至可以避免 kernel-space 副本:数据从线路直接进入其最终内存位置。
  • 当处理文件(而不是网络)时,可以直接从磁盘mmap() 非常大的消息并直接使用映射的内存区域。这样做是 O(1)——文件有多大并不重要。当您实际访问文件的必要部分时,您的操作系统将自动分页。
  • 同一台机器上的两个进程可以通过没有副本的共享内存段进行通信。请注意,通常,常规的旧 C++ 对象在共享内存中不能很好地工作,因为内存段在两个内存 space 中通常没有相同的地址,因此所有指针都是错误的。使用 zero-copy 序列化框架,指针通常表示为偏移量而不是绝对地址,因此它们是 position-independent.

Boost 和 Cereal 不同:当您在这些系统中收到消息时,首先会对整个消息执行传递以 "unpack" 内容。数据的最终存放位置是使用 new/delete 以传统方式分配的对象。类似地,在发送消息时,必须从这棵对象树中收集数据并将其打包到一个缓冲区中以便写出。尽管 Boost 和 Cereal 是 "extensible",但真正 zero-copy 需要非常不同的底层设计;它不能 bolted-in 作为扩展名。

就是说,不要假设 zero-copy 总是会更快。 memcpy() 可以非常快,而您的程序的其余部分可能会使成本相形见绌。同时,zero-copy 系统往往有不方便的 API,特别是因为对内存分配的限制。总的来说,使用传统的序列化系统可能会更好地利用您的时间。

zero-copy 最明显的优势是在操作文件时,因为正如我提到的,您可以轻松地 mmap() 一个大文件并且只读取其中的一部分。非 zero-copy 格式根本无法做到这一点。但是,当谈到网络时,优势就不那么明显了,因为网络通信本身必然是 O(n)。

归根结底,如果您真的想知道哪种序列化系统对您的用例来说最快,您可能需要全部尝试并进行测量。请注意,玩具基准通常具有误导性;您需要测试您的实际用例(或非常相似的东西)以获得有用的信息。

披露:我是 Cap'n Proto(一个 zero-copy 序列化器)和 Protocol Buffers v2(一个流行的非 zero-copy 序列化器)的作者。