在阅读了几篇关于Spark的DataSet的精彩文章(this,this和this)后,我完成了下一个DataSet相对于RDD的性能优势:
问题:
RDD[Person]
的示例。 DataSet是否具有高级典型化? IN_MEMORY_ONLY
持久性策略时,情况怎么样?无论如何,DataSet会将所有内容序列化吗?它会比RDD有任何性能优势吗? 答案 0 :(得分:5)
Spark的RDD还可以构建物理计划,并可以在同一阶段组合/优化多个转换。比DataSet相对于RDD有什么好处?
使用RDD时,你所写的是你得到的。虽然通过链接优化了某些转换,但执行计划是DAG的直接转换。例如:
rdd.mapPartitions(f).mapPartitions(g).mapPartitions(h).shuffle()
其中shuffle
是任意改组转换(*byKey
,repartition
等)所有三个mapPartitions
(map
,flatMap
, <{1}})将被链接而不创建中间对象,但不能重新排列。
与filter
相比,使用明显更具限制性的编程模型,但可以使用多种技术优化执行,包括:
选择(Datasets
)下推。例如,如果你有:
filter
可以执行:
df.withColumn("foo", col("bar") + 1).where(col("bar").isNotNull())
早期预测(df.where(col("bar").isNotNull()).withColumn("foo", col("bar") + 1)
)和淘汰。例如:
select
可以改写为:
df.withColumn("foo", col("bar") + 1).select("foo", "bar")
以避免获取和传递过时的数据。在极端情况下,它可以完全消除特定的转换:
df.select("foo", "bar").withColumn("foo", col("bar") + 1)
可以优化到
df.withColumn("foo", col("bar") + 1).select("bar")
这些优化可能有两个原因:
为了说清楚,我们假设我们有以下数据模型:
df.select("bar")
我们希望检索21岁以上所有人的姓氏。case class Person(name: String, surname: String, age: Int)
val people: RDD[Person] = ???
可以表示为:
RDD
现在让我们问几个问题:
people
.map(p => (p.surname, p.age)) // f
.filter { case (_, age) => age > 21 } // g
中的输入age
与f
的{{1}}变量之间的关系是什么?age
然后g
与f
相同,然后是g
吗?g
和f
副作用是否免费?虽然答案对于人类读者来说是显而易见的,但它不适用于假设的优化器。与f
版本相比:
g
优化者和人类读者都明白答案。
使用静态类型Dataframe
(Spark 2.0 Dataset vs DataFrame)时会产生一些进一步的后果。
让DataSet获得更高级的典型化吗?
people.toDF
.select(col("surname"), col("age")) // f'
.where(col("age") > 21) // g'
,目前无法对复杂类型层次结构进行编码。“矢量化操作”是什么意思?
在优化的上下文中,我们通常意味着循环矢量化/循环展开。 Spark SQL使用代码生成来创建高级转换的编译器友好版本,可以进一步优化以利用向量化指令集。
据我所知,DataSet的低内存管理=高级序列化。
不完全是。使用本机分配的最大优点是转义垃圾收集器循环。由于垃圾收集通常是Spark中的一个限制因素,因此这是一个巨大的改进,特别是在需要大型数据结构(如准备shuffle)的环境中。
另一个重要方面是柱状存储,它可以实现有效压缩(可能更低的内存占用)和对压缩数据的优化操作。
通常,您可以在普通Datasets
上使用手工制作的代码应用完全相同类型的优化。所有Dataset[Row]
都由RDDs
支持。不同之处仅在于需要多少努力。
Datasets
本机内存分配不适合胆小的人。尽管它的所有优点RDDs
API并不普及。虽然某些类型的常见任务可以在许多情况下从其优化中受益,但与RDD等效项相比,您可能无论如何都没有任何改进甚至性能下降。