RDD API与UDF混合使用DataFrame API的性能影响

时间:2016-08-09 21:34:19

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

(Scala特定问题。)

尽管Spark文档鼓励尽可能使用DataFrame API,但如果DataFrame API不足,则通常选择回退到RDD API或使用UDF。这两种选择之间是否存在固有的性能差异?

RDD和UDF类似,因为它们都不能从Catalyst和Tungsten优化中受益。是否还有其他开销,如果存在,两种方法之间是否存在差异?

举一个具体的例子,让我说我有一个DataFrame,其中包含一列带有自定义格式的文本数据(不适合regexp匹配)。我需要解析该列并添加一个包含结果标记的新向量列。

2 个答案:

答案 0 :(得分:11)

  

它们都不能从Catalyst和Tungsten优化中受益

这不完全正确。虽然UDF不会从Tungsten优化中受益(可以说简单的SQL转换也不会得到巨大的推动),但您仍然可以从Catalyst提供的执行计划优化中受益。让我们举一个简单的例子来说明(注意:Spark 2.0和Scala。不要将它推断到早期版本,尤其是使用PySpark):

val f = udf((x: String) => x == "a")
val g = udf((x: Int) => x + 1)

val df = Seq(("a", 1), ("b", 2)).toDF

df
  .groupBy($"_1")
  .agg(sum($"_2").as("_2"))
  .where(f($"_1"))
  .withColumn("_2", g($"_2"))
  .select($"_1")
  .explain

// == Physical Plan ==
// *HashAggregate(keys=[_1#2], functions=[])
// +- Exchange hashpartitioning(_1#2, 200)
//    +- *HashAggregate(keys=[_1#2], functions=[])
//       +- *Project [_1#2]
//          +- *Filter UDF(_1#2)
//             +- LocalTableScan [_1#2, _2#3]

执行计划向我们展示了几件事:

  • Selection在聚合之前已被推下。
  • Projection在聚合之前已经被推下并有效地删除了第二个UDF调用。

根据数据和管道的不同,这几乎可以免费提供显着的性能提升。

据说,RDD和UDF都要求安全和不安全之间的迁移,后者的灵活性要低得多。尽管如此,如果你需要的只是一个简单的map - 类似的行为而没有初始化昂贵的对象(比如数据库连接),那么UDF就是你的选择。

在稍微复杂的情况下,您可以轻松下载到通用Dataset并保留RDDs,以便在您真正需要访问某些低级功能(如自定义分区)时使用。

答案 1 :(得分:0)

(注意:我没有为此提供测量支持)

对我来说,shuffle 和(反)序列化是主要的成本。但在这些之后,拥有干净的代码才是最重要的。考虑到这一点:

使用 RDD 操作的主要缺点是需要(反)序列化/成完整的 jvm 对象。虽然使用 udf 可能只会(反)序列化所需的列。请注意,这是在处理面向列的数据(例如镶木地板)时,对于我不知道的其他数据格式,但希望在许多情况下两者具有相似的性能。

因此,如果您的算法主要是过滤和改组操作,和/或可以简单地用数据帧操作和本地 udf 表示,您应该使用它们。但是,如果您的算法需要对多列进行复杂处理,最好预先进行反序列化,并在 jvm 对象上执行干净高效的 Scala 代码。

因此,根据我实现复杂数学算法的个人经验,我通常将代码分为两步:

  1. pure dataframe op 可以做尽可能多的过滤、join 和 groupBy 操作。在极少数情况下,我可以在需要无法使用数据帧方法表达的特定本地操作时使用 udf(如果它只需要很少的列)
  2. 然后转换为 rdd 并为数学和/或复杂查找部分使用(平面)映射操作