为什么在DataFrame.rdd.map上使用DataFrame.select(反之亦然)?

时间:2016-11-25 11:57:19

标签: performance apache-spark dataframe apache-spark-sql rdd

select上使用DataFrame来获取我们需要的信息并将基础RDD的每一行映射到同一目的之间是否存在“机械”差异?

“机械”我指的是执行操作的机制。换句话说,实施细节。

哪两个提供更好/更高效?

df = # create dataframe ...
df.select("col1", "col2", ...)

df = # create dataframe ...
df.rdd.map(lambda row: (row[0], row[1], ...))

我正在进行性能测试,因此我将找出哪个更快但我想知道实现差异和优缺点。

2 个答案:

答案 0 :(得分:2)

RDD只是转换和行动的图谱。

DataFrame有一个逻辑计划,在执行操作之前由Catalyst逻辑查询优化器进行内部优化。

在你的情况下意味着什么?

如果你有DataFrame,那么你应该使用select - 任何额外的工作,如过滤,加入等,都将得到优化。优化的DataFrame可以比普通RDD快10倍。换句话说,在执行select之前,Spark会尝试更快地进行查询。使用dataFrame.rdd.map()

时无法完成此操作

另外一个:rdd值通过以下方式懒散计算:

lazy val rdd: RDD[T] = {
    val objectType = exprEnc.deserializer.dataType
    val deserialized = CatalystSerde.deserialize[T](logicalPlan)
    sparkSession.sessionState.executePlan(deserialized).toRdd.mapPartitions { rows =>
      rows.map(_.get(0, objectType).asInstanceOf[T])
    }
  }

所以Spark会使用它的RDD,地图和演员内容。两个版本的DAG在查询中几乎相同,如此问题,因此性能将类似。然而,在更高级的情况下,使用数据集的好处将非常明显,正如Spark PMCs在Databricks博客上所写,经过Catalyst优化后数据集甚至可以快100倍

请注意,DataFrame = Dataset [Row]并且它在后台使用RDD - 但RDD的图形是在优化后创建的

注意:Spark正在统一API。 Spark ML现在以DataFrame为中心,不应使用旧API。流媒体正在转向结构化流媒体。因此,即使您的性能不会提高很多,也可以考虑使用DataFrames。这对未来的发展会有更好的决定,当然比使用普通的RDD更快

答案 1 :(得分:1)

在这个带有DataFrame.selectDataFrame.rdd.map的简化示例中,我认为差异可能几乎可以忽略不计。

毕竟你已经加载了你的数据集,只做了投影。最终,两者都必须从Spark的InternalRow列式格式反序列化数据,以计算操作的结果。

您可以通过DataFrame.select检查explain(extended = true)会发生什么情况,在那里您将了解实际计划(以及实际计划)。

scala> spark.version
res4: String = 2.1.0-SNAPSHOT

scala> spark.range(5).select('id).explain(extended = true)
== Parsed Logical Plan ==
'Project [unresolvedalias('id, None)]
+- Range (0, 5, step=1, splits=Some(4))

== Analyzed Logical Plan ==
id: bigint
Project [id#17L]
+- Range (0, 5, step=1, splits=Some(4))

== Optimized Logical Plan ==
Range (0, 5, step=1, splits=Some(4))

== Physical Plan ==
*Range (0, 5, step=1, splits=Some(4))

将物理计划(即SparkPlan)与您使用rdd.maptoDebugString)所做的事情进行比较,您就会知道什么可能“更好”。

scala> spark.range(5).rdd.toDebugString
res5: String =
(4) MapPartitionsRDD[8] at rdd at <console>:24 []
 |  MapPartitionsRDD[7] at rdd at <console>:24 []
 |  MapPartitionsRDD[6] at rdd at <console>:24 []
 |  MapPartitionsRDD[5] at rdd at <console>:24 []
 |  ParallelCollectionRDD[4] at rdd at <console>:24 []

(再次在这个人为的例子中,我认为没有赢家 - 两者都尽可能高效。)

请注意,DataFrame实际上是Dataset[Row],它使用RowEncoder将数据编码(即序列化)为InternalRow列二进制格式。如果您要在管道中执行更多运算符,那么仅仅因为低级别的幕后逻辑查询计划优化和柱状二进制文件,您可以通过坚持Dataset而不是RDD获得更好的性能格式。

有很多优化措施,试图击败它们可能会浪费你的时间。你必须全心全意地了解Spark内部以获得更好的性能(价格肯定是可读性)。

有很多内容,我强烈建议观看Herman van Hovell的谈话A Deep Dive into the Catalyst Optimizer,以了解和欣赏所有的优化。

我对它的看法是...... “远离RDD,除非你知道你在做什么”