虽然我的第一直觉是将DataFrames
用于所有事情,但这是不可能的 - 某些操作显然更容易和/或更好地执行RDD
操作,更不用说确定了像GraphX
这样的API仅适用于RDDs
。
这些天我似乎花了很多时间在DataFrames
和RDDs
之间来回转换 - 那么性能如何?拿RDD.checkpoint
- 那里没有DataFrame
等价物,所以当我这样做时会发生什么:
val df = Seq((1,2),(3,4)).toDF("key","value")
val rdd = df.rdd.map(...)
val newDf = rdd.map(r => (r.getInt(0), r.getInt(1))).toDF("key","value")
显然,这是一个非常小的例子,但是很高兴知道转换中场景背后会发生什么。
答案 0 :(得分:3)
让我们先看看df.rdd
。这被定义为:
lazy val rdd: RDD[Row] = {
// use a local variable to make sure the map closure doesn't capture the whole DataFrame
val schema = this.schema
queryExecution.toRdd.mapPartitions { rows =>
val converter = CatalystTypeConverters.createToScalaConverter(schema)
rows.map(converter(_).asInstanceOf[Row])
}
}
首先,它运行queryExecution.toRdd
,它基本上根据用于构建DataFrame的运算符准备执行计划,并计算表示计划结果的RDD[InternalRow]
。
接下来,该RDD的这些InternalRow
(仅供内部使用)将映射到普通Row
。每行需要以下内容:
override def toScala(row: InternalRow): Row = {
if (row == null) {
null
} else {
val ar = new Array[Any](row.numFields)
var idx = 0
while (idx < row.numFields) {
ar(idx) = converters(idx).toScala(row, idx)
idx += 1
}
new GenericRowWithSchema(ar, structType)
}
}
所以它遍历所有元素,将它们转换为'scala'空间(来自Catalyst空间),并用它们创建最后一行。 toDf
几乎可以反过来做这些事情。
这一切确实会对您的表现产生一些影响。多少取决于这些操作与您使用数据执行的操作相比的复杂程度。然而,更大的可能影响是Spark的Catalyst优化器只能优化RDD转换之间的操作,而不是优化整个执行计划。看看哪些操作有问题会很有趣,我发现大多数事情都可以使用基本表达式或UDF来完成。使用仅适用于RDD的模块是一个非常有效的用例!