54

对于我正在考虑为即将到来的项目做的一些缓存,我一直在考虑 Java 序列化。即,应该使用它吗?

现在我以前写过自定义序列化和反序列化(Externalizable),因为过去几年的各种原因。如今,互操作性已成为一个更大的问题,我可以预见到需要与.Net 应用程序交互,因此我考虑使用独立于平台的解决方案。

有没有人有过高性能使用 GPB 的经验?它在速度和效率方面与 Java 的原生序列化相比如何?或者,还有其他值得考虑的方案吗?

4

8 回答 8

61

我没有在速度方面将 Protocol Buffers 与 Java 的本机序列化进行比较,但对于互操作性,Java 的本机序列化是一个严重的禁忌。在大多数情况下,它在空间方面也不会像协议缓冲区那样有效。当然,它在可以存储的内容和引用等方面更加灵活。Protocol Buffers 非常擅长它的用途,当它满足您的需要时它很棒 - 但是由于互操作性存在明显的限制(和其他东西)。

我最近发布了一个用 Java 和 .NET 编写的 Protocol Buffers 基准测试框架。Java 版本在Google 主项目中(在benchmarks 目录中),.NET 版本在我的 C# 端口项目中。如果您想将 PB 速度与 Java 序列化速度进行比较,您可以编写类似的类并对其进行基准测试。如果您对互操作感兴趣,我真的不会再考虑原生 Java 序列化(或 .NET 原生二进制序列化)。

除了 Protocol Buffers 之外,还有其他可互操作的序列化选项 - ThriftJSONYAML 浮现在脑海中,毫无疑问还有其他选项。

编辑:好的,由于互操作不是那么重要,因此值得尝试从序列化框架中列出您想要的不同品质。您应该考虑的一件事是版本控制——这是 PB 旨在处理好的另一件事,无论是向后还是向前(所以新软件可以读取旧数据,反之亦然)——当然,当你坚持建议的规则时 :)

在尝试对 Java 性能与本机序列化保持谨慎之后,我真的不会惊讶地发现 PB 无论如何都更快。如果有机会,请使用服务器虚拟机——我最近的基准测试显示服务器虚拟机在序列化和反序列化示例数据方面的速度是原来的两倍多。我认为 PB 代码非常适合服务器 VM 的 JIT :)

正如示例性能数据一样,序列化和反序列化两条消息(一条 228 字节,一条 84750 字节)我使用服务器 VM 在笔记本电脑上得到了这些结果:

使用文件 google_message1.dat 对 benchmarks.GoogleSize$SizeMessage1 进行基准测试
序列化为字节串:30.16s 2581851 次迭代;18.613789MB/s
序列化为字节数组:29.842s 内迭代 2583547 次;18.824497MB/s
序列化到内存流:2210320次迭代,30.125s;15.953759MB/s
从字节串反序列化:30.088s 内迭代 3356517 次;24.256632MB/s
从字节数组反序列化:29.958s 内迭代 3356517 次;24.361889MB/s
从内存流中反序列化:29.821s 内迭代 2618821 次;19.094952MB/s

使用文件 google_message1.dat 对 benchmarks.GoogleSpeed$SpeedMessage1 进行基准测试
序列化为字节串:29.978s 内迭代 17068518 次;123.802124MB/s
序列化为字节数组:17520066次迭代,30.043s;126.802376MB/s
序列化到内存流:7736665次迭代,30.076s;55.93307MB/秒
从字节串反序列化:16123669次迭代,30.073s;116.57947MB/s
从字节数组反序列化:16082453次迭代,30.109s;116.14243MB/s
从内存流中反序列化:30.03s 内迭代 7496968 次;54.283176MB/s

使用文件 google_message2.dat 对 benchmarks.GoogleSize$SizeMessage2 进行基准测试
序列化为字节串:30.034s 6266 次迭代;16.826494MB/s
序列化为字节数组:30.027s 内迭代 6246 次;16.776697MB/s
序列化到内存流:29.916s 内 6042 次迭代;16.288969MB/s
从字节串反序列化:29.819s 内迭代 4675 次;12.644595MB/s
从字节数组反序列化:30.093s 4694 次迭代;12.580387MB/s
从内存流中反序列化:29.579s 内迭代 4544 次;12.389998MB/s

使用文件 google_message2.dat 对 benchmarks.GoogleSpeed$SpeedMessage2 进行基准测试
序列化为字节串:30.055s 内迭代 39562 次;106.16416MB/s
序列化为字节数组:30.178s 内迭代 39715 次;106.14035MB/s
序列化到内存流:30.032s 内迭代 34161 次;91.74085MB/秒
从字节串反序列化:29.794s 内迭代 36934 次;99.98019MB/s
从字节数组反序列化:29.915s 内迭代 37191 次;100.26867MB/s
从内存流中反序列化:29.846s 内迭代 36237 次;97.92251MB/秒

“速度”与“大小”是生成的代码是否针对速度或代码大小进行了优化。(两种情况下的序列化数据是相同的。“大小”版本是为您定义了很多消息并且不想为代码占用大量内存的情况提供的。)

如您所见,对于较小的消息,它可以非常快——每毫秒序列化或反序列化超过 500 条小消息。即使使用 87K 消息,每条消息也只需不到一毫秒的时间。

于 2009-03-15T13:16:52.583 回答
15

另一个数据点:这个项目:

http://code.google.com/p/thrift-protobuf-compare/

给出了小对象的预期性能的一些想法,包括 PB 上的 Java 序列化。

结果因您的平台而异,但有一些总体趋势。

于 2009-03-31T18:17:53.307 回答
8

您还可以查看FST,它是内置 JDK 序列化的直接替代品,应该更快且输出更小。

对我近年来所做的频繁基准测试的原始估计:

100% = 基于二进制/结构的方法(例如 SBE、fst-structs)

  • 不方便
  • 后处理(在接收端建立“真实”对象)可能会消耗性能优势,并且永远不会包含在基准测试中

~10%-35% protobuf 和衍生物

~10%-30% 快速串行器,例如 FST 和 KRYO

  • 方便,反序列化的对象可以直接使用,无需额外的手动翻译代码。
  • 可以为性能而拉皮条(注释,类注册)
  • 保留对象图中的链接(没有对象序列化两次)
  • 可以处理循环结构
  • 通用方案,FST完全兼容JDK序列化

~2%-15% JDK 序列化

~1%-15% 快速 JSON(例如 Jackson)

  • 不能处理任何对象图,只能处理 Java 数据结构的一小部分
  • 没有参考恢复

0.001-1% 全图 JSON/XML(例如 JSON.io)

这些数字旨在给人一种非常粗略的数量级印象。请注意,性能很大程度上取决于被序列化/基准测试的数据结构。所以单一的简单类基准大多是无用的(但很流行:例如忽略 unicode,没有集合,..)。

也可以看看

http://java-is-the-new-c.blogspot.de/2014/12/a-persistent-keyvalue-server-in-40.html

http://java-is-the-new-c.blogspot.de/2013/10/still-using-externalizable-to-get.html

于 2012-12-06T22:17:33.830 回答
7

你说的高性能是什么意思?如果你想要毫秒级的序列化,我建议你使用最简单的序列化方法。如果您想要亚毫秒,您可能需要二进制格式。如果您想要远低于 10 微秒,您可能需要自定义序列化。

我还没有看到很多序列化/反序列化的基准测试,但很少有人支持少于 200 微秒的序列化/反序列化。

与平台无关的格式是有代价的(在您的努力和延迟方面),您可能必须决定是否需要性能或平台独立性。但是,没有理由不能将两者都作为配置选项,您可以根据需要在它们之间切换。

于 2009-03-15T13:53:26.583 回答
6

如果您在速度和效率方面对 PB 和本机 java 序列化感到困惑,那就选择 PB。

  • PB 旨在实现这些因素。请参阅http://code.google.com/apis/protocolbuffers/docs/overview.html
  • PB 数据非常小,而 java 序列化倾向于复制整个对象,包括其签名。为什么我总是得到我的类名,字段名......序列化,即使我在接收者那里完全知道它?
  • 考虑跨语言发展。如果一侧使用 Java,一侧使用 C++,那就越来越难了……

一些开发人员建议使用 Thrift,但我会使用 Google PB,因为“我相信 google”:-).. 无论如何,值得一看: http ://stuartsierra.com/2008/07/10/thrift-vs-protocol -缓冲区

于 2009-03-15T17:25:05.403 回答
1

这是当天的建议:-)(你刚刚在我的脑海中调整了一些我现在想尝试的东西)......

如果您可以通过它寻求整个缓存解决方案,它可能会起作用:Project Darkstar。它被设计为非常高性能的游戏服务器,特别是读取速度很快(非常适合缓存)。它有 Java 和 C API,所以我相信(我认为已经很久没有看到它了,当时我没有想到这一点)你可以用 Java 保存对象并用 C 读回它们,反之亦然。

如果没有别的,它会给你今天阅读的东西:-)

于 2009-03-15T15:36:35.180 回答
1

对于有线友好的序列化,请考虑使用 Externalizable 接口。巧妙地使用,您将拥有深入的知识来决定如何以最佳方式编组和解组特定字段。也就是说,您需要正确管理每个对象的版本控制 - 易于取消编组,但是当您的代码支持 V1 时重新编组 V2 对象会破坏、丢失信息或更糟地破坏您的应用程序的数据无法正确处理。如果您正在寻找最佳路径,请注意没有任何库可以在不妥协的情况下解决您的问题。通常,如果您选择了一个活跃的开源项目,库将适合大多数用例,并且会带来额外的好处,即它们会随着时间的推移而适应和增强,而无需您的输入。他们可能会增加性能问题,引入错误,

于 2015-06-09T21:06:39.373 回答
0

If you want to solve the problem of cross-platform and cross-language data exchange, you can consider using FastProto (https://github.com/indunet/fastproto) as your serialization tool, through annotating custom protocols, which has extremely high efficiency.

于 2021-08-12T07:25:29.667 回答