使用编码器如何比 java 序列化快得多?

Posted

技术标签:

【中文标题】使用编码器如何比 java 序列化快得多?【英文标题】:How is using encoders so much faster than java serialization? 【发布时间】:2018-05-05 08:11:31 【问题描述】:

如何使用编码器比 java 和 kryo 序列化快得多?

【问题讨论】:

【参考方案1】:

因为Encoders 牺牲了通用性来换取性能。这个想法并不新鲜。为什么 Kryo 比 Java 序列化更快?出于同样的原因。考虑一下这个成绩单:

scala> val spark = SparkSession.builder.config("spark.serializer", "org.apache.spark.serializer.JavaSerializer").getOrCreate()
spark: org.apache.spark.sql.SparkSession = org.apache.spark.sql.SparkSession@1ed28f57

scala> val map = Map[String, Int]("foo" -> 1).withDefaultValue(0)
map: scala.collection.immutable.Map[String,Int] = Map(foo -> 1)

scala> map("bar")
res1: Int = 0

scala> val mapSerDe = spark.sparkContext.parallelize(Seq(map)).first
mapSerDe: scala.collection.immutable.Map[String,Int] = Map(foo -> 1)

scala> mapSerDe("bar")
res2: Int = 0

相比

scala> val spark = SparkSession.builder.config("spark.serializer", "org.apache.spark.serializer.KryoSerializer").getOrCreate()
spark: org.apache.spark.sql.SparkSession = org.apache.spark.sql.SparkSession@5cef3456

scala>  val map = Map[String, Int]("foo" -> 1).withDefaultValue(0)
map: scala.collection.immutable.Map[String,Int] = Map(foo -> 1)

scala> map("bar")
res7: Int = 0

scala>  val mapSerDe = spark.sparkContext.parallelize(Seq(map)).first
mapSerDe: scala.collection.immutable.Map[String,Int] = Map(foo -> 1)

scala> mapSerDe("bar")
java.util.NoSuchElementException: key not found: bar
  at scala.collection.MapLike$class.default(MapLike.scala:228)
  at scala.collection.AbstractMap.default(Map.scala:59)
  at scala.collection.MapLike$class.apply(MapLike.scala:141)
  at scala.collection.AbstractMap.apply(Map.scala:59)
  ... 48 elided

(我找不到确切的帖子,但这个例子的想法来自开发者列表)。

如您所见,Kryo 虽然速度更快,但并不能处理所有可能的情况。它专注于最常见的问题,并且做得对。

Spark Encoders 做同样的事情,但更不通用。如果您只支持 16 种左右的类型,并且不关心互操作性(必须与真正的序列化库具有),那么您有很多机会进行优化。

无需互操作性,您可以走得更远。原子类型的编码器只是身份。根本不需要任何转换。

如himanshuIIITian 所述,了解架构是另一个因素。

为什么重要?因为具有明确定义的形状,允许您优化序列化和存储。如果您知道您的数据是结构化的,您可以切换维度 - 而不是存储和访问成本高昂的异构行,您可以使用列式存储。

一旦数据存储在列中,您就会打开一组全新的优化机会:

固定大小字段的数据访问速度非常快,因为您可以直接访问特定地址(还记得堆外/本机内存/Tungsten 的所有令人兴奋的事情吗?)。 您可以使用多种压缩和编码技术来最小化数据大小。

这个想法也不新鲜。列式数据库、存储格式(如 Parquet)或为分析而设计的现代序列化格式(如 Arrow)使用相同的想法,并且经常将这些想法推得更远(零拷贝数据共享)。

不幸的是,编码器不是灵丹妙药。存储非标准对象is a mess,集合Encoders可以是very inefficient。

【讨论】:

非常感谢。您能否详细说明 Kryo mapSerDe("bar") 失败的原因以及 Kryo 对哪些类型不起作用?这会给我一个清晰的画面。再次感谢! 它失败了,因为序列化逻辑只查看键值对。它完全不知道默认值等细微之处。 知道了!谢谢!【参考方案2】:

简单 - 编码器知道记录的架构。这就是它们提供显着更快的序列化和反序列化的方式(与默认的 Java 或 Kryo 序列化程序相比)。

供参考 - https://jaceklaskowski.gitbooks.io/mastering-spark-sql/spark-sql-Encoder.html

【讨论】:

谢谢。我正在寻找更详细的比较。您能否解释一下没有模式如何使序列化变得更加困难?仅仅是因为 tungsten 编码更紧凑,因为我们有模式并且我们正在序列化减少的内容吗?

以上是关于使用编码器如何比 java 序列化快得多?的主要内容,如果未能解决你的问题,请参考以下文章

Java Selenium CI 构建运行速度比本地快得多?

Android:为什么本机代码比Java代码要快得多

为啥 memcmp 比 for 循环检查快得多?

为啥'select * from Employee group by Id'比直接使用Employee表快得多?

为啥用 C 复制文件比 C++ 快得多?

为啥字典比列表快得多?