【问题标题】:Fastest serialization/deserialization of Scala case classesScala案例类的最快序列化/反序列化
【发布时间】:2013-08-12 04:10:37
【问题描述】:

如果我有一个案例类的嵌套对象图,类似于下面的示例,并且我想将它们的集合存储在一个 redis 列表中,我应该查看哪些库或工具可以提供最快的整体往返 redis?

这将包括:

  • 是时候序列化项目了
  • 传输序列化数据的网络成本
  • 检索存储的序列化数据的网络成本
  • 是时候反序列化回案例类了

    case class Person(name: String, age: Int, children: List[Person]) {}
    

【问题讨论】:

  • upickle 快速且易于使用,有关详细信息,请参阅我的答案。接受的答案已过时。我的答案有一个更新的 Scala 序列化基准测试的链接。
  • 使用 jsoniter-scala 进行 JSON 的超快速解析和序列化。 Here 是基准测试的结果,它与使用不同数据类型和消息样本的 Scala 的现代 JSON 解析器进行比较。

标签: scala redis


【解决方案1】:

更新(2018 年):不再积极维护 scala/pickling。有大量其他库作为替代方案出现,它们采用类似的方法,但往往专注于特定的序列化格式;例如,JSON、二进制、protobuf。

您的用例正是 scala/pickling (https://github.com/scala/pickling) 的目标用例。 免责声明:我是作者。

Scala/pickling 旨在成为 Java 或 Kryo 等自动框架的更快、更安全、更开放的替代方案。它是专为分布式应用程序而构建的,因此序列化/反序列化时间和序列化数据大小占据首位。它采用不同的方法进行序列化 - 它在编译时在使用站点内联生成酸洗(序列化)代码,因此速度非常快。

最新的基准在我们的OOPSLA paper- 中针对二进制 pickle 格式(您也可以选择其他格式,例如 JSON),scala/pickling 始终比 Java 和 Kryo 快,并且生成的二进制表示与 Java 和 Kryo 相当或小于Kryo's,意味着通过网络传递腌制数据时的延迟更少。

更多信息,有一个项目页面: http://lampwww.epfl.ch/~hmiller/pickling

还有一个ScalaDays 2013 talk from June on Parley's。

我们还将在 Strange Loop 2013 上展示一些新的发展,特别是与处理通过网络发送闭包相关的,以防这也可能成为您用例的痛点。

截至撰写本文时,scala/pickling 处于预发布阶段,我们的第一个稳定版本计划于 8 月 21 日发布。

【讨论】:

  • 这看起来很有用,但是github页面有点短。我一定会看看那些外观并感谢您的回复。
  • *链接。顺便说一句,JSON 是性能最佳的格式,还是只是 github 上的示例?
  • 是的,我们正在处理文档。随着版本的发布,接下来一两周内将会有更多的使用说明。我们的二进制格式是我们最优化性能的格式。虽然 JSON 也应该很快(我们还没有发布 JSON 的综合基准)。
  • 很遗憾pickling 不支持枚举类型。
  • 该项目似乎不再维护了。或者即将成为废弃软件。
【解决方案2】:

更新:

您必须小心使用 JDK 中的序列化方法。性能不是很好,你的类中的一个小改动就会导致数据无法反序列化。


我使用过 scala/pickling,但它在序列化/反序列化时有一个全局锁。

所以我没有使用它,而是像这样编写自己的序列化/反序列化代码:

import java.io._

object Serializer {

  def serialize[T <: Serializable](obj: T): Array[Byte] = {
    val byteOut = new ByteArrayOutputStream()
    val objOut = new ObjectOutputStream(byteOut)
    objOut.writeObject(obj)
    objOut.close()
    byteOut.close()
    byteOut.toByteArray
  }

  def deserialize[T <: Serializable](bytes: Array[Byte]): T = {
    val byteIn = new ByteArrayInputStream(bytes)
    val objIn = new ObjectInputStream(byteIn)
    val obj = objIn.readObject().asInstanceOf[T]
    byteIn.close()
    objIn.close()
    obj
  }
}

这是一个使用它的例子:

case class Example(a: String, b: String)

val obj = Example("a", "b")
val bytes = Serializer.serialize(obj)
val obj2 = Serializer.deserialize[Example](bytes)

【讨论】:

    【解决方案3】:

    根据upickle 基准:“uPickle 在读/写方面比 Circe 快 30-50%,在序列化案例类方面比 play-json 快约 200%”。

    很容易使用,下面介绍如何将case类序列化为JSON字符串:

    case class City(name: String, funActivity: String, latitude: Double)
    val bengaluru = City("Bengaluru", "South Indian food", 12.97)
    implicit val cityRW = upickle.default.macroRW[City]
    upickle.default.write(bengaluru) // "{\"name\":\"Bengaluru\",\"funActivity\":\"South Indian food\",\"latitude\":12.97}"
    

    您还可以序列化为二进制或其他格式。

    【讨论】:

      【解决方案4】:

      2013 年接受的答案提出了一个不再维护的库。 StackOverflow 上有很多类似的问题,但我真的找不到符合以下条件的好答案:

      • 序列化/反序列化应该很快
      • 通过网络进行高性能数据交换,您只需编码所需的尽可能多的元数据
      • 支持模式演变,以便更改序列化对象(例如:case class)不会破坏过去的反序列化

      我建议不要使用低级 JDK SerDes(如 ByteArrayOutputStream 和 ByteArrayInputStream)。支持模式演变变得很痛苦,并且很难使其与外部服务(例如:Thrift)一起使用,因为您无法控制发回的数据是否使用相同类型的流。

      有些人使用 JSON 规范,使用像 json4s 这样的库,但它不适合分布式计算消息传输。它将数据编组为 JSON 字符串,因此速度较慢且存储效率低,因为它将使用 8 位来存储字符串中的每个字符。

      我强烈推荐使用MessagePack 二进制序列化格式。我建议阅读规范以了解编码细节。它具有多种不同语言的实现,这是我为 Scala case class 编写的通用示例,您可以将其复制粘贴到您的代码中。

      import java.nio.ByteBuffer
      import java.util.concurrent.TimeUnit
      
      import org.msgpack.core.MessagePack
      
      case class Data(message: String, number: Long, timeUnit: TimeUnit, price: Long)
      
      object Data extends App {
      
        def serialize(data: Data): ByteBuffer = {
          val packer = MessagePack.newDefaultBufferPacker
          packer
            .packString(data.message)
            .packLong(data.number)
            .packString(data.timeUnit.toString)
            .packLong(data.price)
          packer.close()
          ByteBuffer.wrap(packer.toByteArray)
        }
      
        def deserialize(data: ByteBuffer): Data = {
          val unpacker = MessagePack.newDefaultUnpacker(convertDataToByteArray(data))
          val newdata = Data.apply(
            message = unpacker.unpackString(),
            number = unpacker.unpackLong(),
            timeUnit = TimeUnit.valueOf(unpacker.unpackString()),
            price = unpacker.unpackLong()
          )
          unpacker.close()
          newdata
        }
      
        def convertDataToByteArray(data: ByteBuffer): Array[Byte] = {
          val buffer = Array.ofDim[Byte](data.remaining())
          data.duplicate().get(buffer)
          buffer
        }
      
        println(deserialize(serialize(Data("Hello world!", 1L, TimeUnit.DAYS, 3L))))
      }
      

      它将打印:

      Data(Hello world!,1,DAYS,3)
      

      【讨论】:

        猜你喜欢
        • 2017-03-29
        • 2013-08-04
        • 1970-01-01
        • 2015-03-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-23
        • 2011-06-29
        相关资源
        最近更新 更多