在编写spark UDF(而不是将UDF作为一个)时会有性能损失吗?

时间:2017-12-07 19:54:43

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

我想通过编写spark udf来判断是否存在性能损失。一般来说,我更喜欢编写一个小功能,只做一件事......

这是一个简单的例子,给出DataFrame df:

def inc = udf( (i: Double) => i + 1)
def double = udf( (i: Double) => i * 2)
df.withColumn("r", double(inc($"c")))

def incAndDouble = udf( (i: Double) => (i + 1) * 2)
df.withColumn("r", incAndDouble($"c")

从我所看到的情况来看,这个简单例子的表现是相同的。

你能解释一下原因吗? Spark如何在幕后工作?

总是如此吗?

[更新]

当一个聪明的组合(不仅仅是一个简单的函数组合)成为可能时,我可能有一个反例,如下例所示

def filter = udf((s: Seq[String]) => s.startsWith("A"))
def size = udf((s: Seq[String]) => s.size)

val filterAndSize = udf((s: Seq[String]) => s.count(_.startsWith("A")))

所以,我认为filterAndSize更可取,因为它会避免一些中间集合的实例化。

1 个答案:

答案 0 :(得分:7)

TL; DR 可能会有一些性能下降或惩罚,但可以忽略不计。

  

你能解释一下原因吗?

用&#34解释你的问题很有趣#34;解释"这正是用于查看Spark SQL的内容以及它如何执行查询的方法的名称:)

因此,使用Dataset.explain甚至更详细的版本Dataset.explain(extended = true)来查看所有优化(以及可能的性能下降)。

def inc = udf( (i: Double) => i + 1)
def double = udf( (i: Double) => i * 2)

val df = Seq(1,2,3).toDF("c")
val q = df.withColumn("r", double(inc($"c")))

使用两个UDF组成的计划如下所示。

scala> q.explain(extended = true)
== Parsed Logical Plan ==
'Project [c#3, UDF(UDF('c)) AS r#10]
+- AnalysisBarrier Project [value#1 AS c#3]

== Analyzed Logical Plan ==
c: int, r: double
Project [c#3, if (isnull(if (isnull(cast(c#3 as double))) null else UDF(cast(c#3 as double)))) null else UDF(if (isnull(cast(c#3 as double))) null else UDF(cast(c#3 as double))) AS r#10]
+- Project [value#1 AS c#3]
   +- LocalRelation [value#1]

== Optimized Logical Plan ==
LocalRelation [c#3, r#10]

== Physical Plan ==
LocalTableScan [c#3, r#10]

让我们看看一个UDF是两个UDF组合的计划。

def incAndDouble = udf( (i: Double) => (i + 1) * 2)
val q = df.withColumn("r", incAndDouble($"c"))
scala> q.explain(extended = true)
== Parsed Logical Plan ==
'Project [c#3, UDF('c) AS r#16]
+- AnalysisBarrier Project [value#1 AS c#3]

== Analyzed Logical Plan ==
c: int, r: double
Project [c#3, if (isnull(cast(c#3 as double))) null else UDF(cast(c#3 as double)) AS r#16]
+- Project [value#1 AS c#3]
   +- LocalRelation [value#1]

== Optimized Logical Plan ==
LocalRelation [c#3, r#16]

== Physical Plan ==
LocalTableScan [c#3, r#16]

在这种特殊情况下,差异是无,因为查询中的物理计划是相同的,即LocalTableScan

它可能与其他数据源(如文件或JDBC)不同,但我个人的建议是尽可能小地开发UDF,因为它们是Spark优化器的黑盒子。

  

总是如此吗?

不,完全没有,因为它在很大程度上取决于你在UDF中做了什么(但这与首先是否编写UDF有关)。

在以下UDF是谓词的情况下(即返回一个布尔值):

def filter = udf((s: Seq[String]) => s.startsWith("A"))

Spark可以优化UDF的使用(如果它一个UDF而是一个简单的filter操作)并将其推送到数据源以加载更少的数据。这可能会对性能产生巨大影响。