高性能的序列化:Java vs Google Protocol Buffers vs …?

对于一些caching,我正在考虑为即将到来的项目做些什么,我一直在考虑Java序列化。 也就是说,是否应该使用?

现在我已经写了自定义序列化和反序列化(Externalizable)在过去几年的各种原因。 现在互操作性已经成为一个更大的问题,我可以预见需要与.Net应用程序交互,所以我想到了使用平台无关的解决scheme。

有没有人有高性能使用GPB的经验? 它如何在速度和效率方面与Java的本地串行化进行比较? 或者,还有其他的scheme值得考虑吗?

在速度方面,我还没有将Protocol Buffers与Java的本地串行化进行比较,但是为了互操作性,Java的本地串行化是一个严肃的禁忌。 在大多数情况下,在协议缓冲区的空间方面也不会那么高效。 当然,就可以存储的内容以及引用等方面而言,它更加灵活。协议缓冲区非常擅长它的目的,当它适合您的需求时,它是很好的 – 但是由于互操作性(和其他东西)。

我最近在Java和.NET中发布了Protocol Buffers基准testing框架。 Java版本位于主要Google项目 (在基准目录中 ),.NET版本位于我的C#port项目中 。 如果您想将PB速度与Java序列化速度进行比较,则可以编写类似的类并对其进行基准testing。 如果你对互操作感兴趣,我真的不会给本地Java序列化(或.NET本地二进制序列化)第二个想法。

除了协议缓冲区之外,还有其他的可互操作的序列化选项–Thrift , JSON和YAML ,但毫无疑问。

编辑:好的,互操作性不是那么重要,值得尝试列出你想要的序列化框架的不同质量。 有一件事你应该考虑的是版本pipe理 – 这是PBdevise的另一件事情,无论是向前还是向前(所以新的软件可以读取旧数据,反之亦然) – 当然你坚持build议的规则:)

在试图对Java性能和原生序列化保持谨慎的同时,我发现PB的速度还是比较快的。 如果有机会,请使用服务器虚拟机 – 我最近的基准testing显示,服务器虚拟机在序列化和反序列化示例数据时速度要快两倍 。 我认为PB代码非常适合服务器虚拟机的JIT 🙂

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

基准testing基准.GoogleSize $ SizeMessage1,文件为google_message1.dat 
序列化为字节string:2581851 30.16s的迭代;  18.613789MB /秒 
序列化为字节数组:在29.842s中进行2583547次迭代;  18.824497MB /秒 
序列化到内存stream:30.125s中的2210320次迭代;  15.953759MB /秒 
从字节string反序列化:在30.088s中反复3356517;  24.256632MB /秒 
从字节数组反序列化:在29.958s中进行3356517次迭代;  24.361889MB /秒 
从内存stream中反序列化:在29.821s中进行2618821次迭代;  19.094952MB /秒 

基准testing基准.GoogleSpeed $ SpeedMessage1,文件为google_message1.dat 
序列化为字节string:17068518在29.978s的迭代;  123.802124MB /秒 
序列化为字节数组:在30.043s中迭代17520066;  126.802376MB /秒 
序列化到内存stream:在30.076s中进行7736665次迭代;  55.93307MB /秒 
从字节string反序列化:16123669 30.073s的迭代;  116.57947MB /秒 
从字节数组反序列化:在10.109s中迭代16082453;  116.14243MB /秒
从内存stream中反序列化:在30.03s中进行7496968次迭代;  54.283176MB /秒 

基准Benchmark.GoogleSize $ SizeMessage2与文件google_message2.dat 
序列化为字节string:在30.034s内进行6266次迭代;  16.826494MB /秒 
序列化为字节数组:在30.027s中进行6246次迭代;  16.776697MB /秒 
序列化为内存stream:在29.916s中进行6042次迭代;  16.288969MB /秒 
从字节string反序列化:在29.819s中进行4675次迭代;  12.644595MB /秒 
从字节数组反序列化:在30.093s中进行4694次迭代;  12.580387MB /秒 
从内存stream中反序列化:在29.579s中进行4544次迭代;  12.389998MB /秒 

使用文件google_message2.dat进行Benchmarking Benchmarks.GoogleSpeed $ SpeedMessage2 
序列化为字节string:在30.055s内进行39562次迭代;  106.16416MB /秒 
序列化为字节数组:在30.178s中进行39715次迭代;  106.14035MB /秒 
序列化到内存stream:在30.032s内进行34161次迭代;  91.74085MB /秒 
从字节string反序列化:在29.794s中进行36934次迭代;  99.98019MB /秒 
从字节数组中反序列化:29.915s中的37191次迭代;  100.26867MB /秒 
从内存stream中反序列化:在29.846s中进行36237次迭代;  97.92251MB /秒 

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

正如你所看到的,对于更小的消息,它可以非常快 – 每毫秒超过500个小消息序列化或反序列化。 即使使用87K消息,每条消息也只需要不到一毫秒的时间。

还有一个数据点:这个项目:

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

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

根据您的平台,结果有很大的不同,但也有一些总体趋势。

如果您在PB和原生Java序列化之间在速度和效率之间感到困惑,那就去PB吧。

  • PB的devise是为了达到这些因素。 请参阅http://code.google.com/apis/protocolbuffers/docs/overview.html
  • PB数据非常小,而Java序列化倾向于复制整个对象,包括其签名。 为什么我总是得到我的课程名称,字段名称…序列化,即使我知道在接收器里面呢?
  • 思考跨语言发展。 如果一方使用Java,一方使用C ++就变得越来越困难…

有些开发人员build议Thrift,但我会使用谷歌PB,因为“我相信谷歌”:-)无论如何,这是值得一看: http : //stuartsierra.com/2008/07/10/thrift-vs-protocol -buffers

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

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

独立于平台的格式需要付出代价(在您的努力和延迟上),您可能需要决定是否需要性能或平台独立性。 但是,没有理由不能将它们作为您根据需要切换的configuration选项。

您也许可以看看FST ,这是embedded式JDK序列化的直接替代品,应该更快,输出更小。

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

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

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

〜10%-35%的protobuf&派生物

〜10%-30%的快速序列化程序,如FST和KRYO

  • 方便,反序列化的对象可以直接使用,而不需要额外的手动翻译代码。
  • 可以用于表演(注释,课程注册)
  • 保留对象图中的链接(没有对象序列化两次)
  • 可以处理循环结构
  • 通用的解决scheme,FST与JDK序列化完全兼容

〜2%-15%的JDK序列化

快1%-15%JSon(如jackson)

  • 不能处理任何对象图,而只能处理java数据结构的一小部分
  • 无法恢复

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

这些数字是为了给一个非常粗略的数量级的印象。 请注意,性能取决于被序列化/基准testing的数据结构上的一大堆。 所以单一的简单的类基准几乎是无用的(但stream行:例如忽略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

这是closures墙上的build议:-)(你只是调整了我脑海中的一些东西,我现在想尝试)…

如果你可以通过这个去完成整个caching解决scheme,它可能会工作: Project Darkstar 。 它被devise成非常高性能的游戏服务器,特别是读取速度快(对于caching很好)。 它有Java和C API,所以我相信(自从我看了很长时间以来,我并没有想到这一点),你可以用Java保存对象,并用C读取它们,反之亦然。

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

对于线友好的序列化,请考虑使用Externalizable接口。 巧妙地使用,你将有亲密的知识决定如何优化马歇尔和解组特定的领域。 也就是说,您需要正确pipe理每个对象的版本控制 – 易于取消编组,但是当您的代码支持V1时,重新编组一个V2对象将会破坏,丢失信息,或以更糟糕的方式损坏数据无法正确处理。 如果您正在寻找最佳path,请注意,没有任何妥协,任何图书馆都无法解决您的问题。 一般来说,图书馆将适合大多数的使用情况,如果您select了一个活跃的开源项目,它们将会带来额外的好处,他们将随着时间的推移而不经过您的input而进行调整和提升。 他们可能会增加性能问题,引入错误,甚至修复还没有影响到你的错误!