Spark Dataframe上的val vs def性能

时间:2019-02-24 23:07:12

标签: scala apache-spark

下面的代码以及有关性能的问题-当然可以想象:

import org.apache.spark.sql.types.StructType

val df = sc.parallelize(Seq(
   ("r1", 1, 1),
   ("r2", 6, 4),
   ("r3", 4, 1),
   ("r4", 1, 2)
   )).toDF("ID", "a", "b")

val ones = df.schema.map(c => c.name).drop(1).map(x => when(col(x) === 1, 1).otherwise(0)).reduce(_ + _)

// or

def ones = df.schema.map(c => c.name).drop(1).map(x => when(col(x) === 1, 1).otherwise(0)).reduce(_ + _)

df.withColumn("ones", ones).explain

在使用def和val的两个物理计划下,它们是相同的:

 == Physical Plan == **def**
 *(1) Project [_1#760 AS ID#764, _2#761 AS a#765, _3#762 AS b#766, (CASE WHEN (_2#761 = 1) THEN 1 ELSE 0 END + CASE WHEN (_3#762 = 1) THEN 1 ELSE 0 END) AS ones#770]
 +- *(1) SerializeFromObject [staticinvoke(class 
 org.apache.spark.unsafe.types.UTF8String, StringType, fromString, assertnotnull(input[0, scala.Tuple3, true])._1, true, false) AS _1#760, assertnotnull(input[0, scala.Tuple3, true])._2 AS _2#761, assertnotnull(input[0, scala.Tuple3, true])._3 AS _3#762]
   +- Scan[obj#759]


 == Physical Plan == **val**
 *(1) Project [_1#780 AS ID#784, _2#781 AS a#785, _3#782 AS b#786, (CASE WHEN (_2#781 = 1) THEN 1 ELSE 0 END + CASE WHEN (_3#782 = 1) THEN 1 ELSE 0 END) AS ones#790]
 +- *(1) SerializeFromObject [staticinvoke(class 
 org.apache.spark.unsafe.types.UTF8String, StringType, fromString, assertnotnull(input[0, scala.Tuple3, true])._1, true, false) AS _1#780, assertnotnull(input[0, scala.Tuple3, true])._2 AS _2#781, assertnotnull(input[0, scala.Tuple3, true])._3 AS _3#782]
    +- Scan[obj#779] 

因此,将进行以下讨论:

  

val vs def性能。

然后:

  • 我认为.explains没有区别。好吧。

  • 在其他地方:val定义时进行评估,def-调用时进行评估。

  • 我假设在这里使用val或def并没有区别,因为它本质上是在循环中,并且存在reduce。这是正确的吗?
  • 是否会针对每个数据帧行执行 df.schema.map(c => c.name).drop(1) ?当然没有必要了。 Catalyst是否可以对此进行优化?
  • 如果以上情况是正确的,那就是每次都要执行该语句以处理各列,那么如何使该段代码仅发生一次?我们是否应该将 val的值设为df.schema.map(c => c.name).drop(1)
  • val,def不仅仅是Scala,还是Spark组件。

对于-1er,我这样问,因为以下内容非常清楚,但是val的内容比下面的代码更多,并且下面的内容没有被迭代:

var x = 2 // using var as I need to change it to 3 later
val sq = x*x // evaluates right now
x = 3 // no effect! sq is already evaluated
println(sq)

2 个答案:

答案 0 :(得分:5)

这里有两个核心概念,Spark DAG创建和评估,以及Scala的valdef定义,它们是正交的

  

我认为.explains没有什么区别

您不会发现任何区别,因为从Spark的角度来看,查询是相同的。对于分析器而言,将图形存储在val中还是每次使用def创建图形都没有关系。

  

从其他地方开始:val定义时进行评估,def-调用时进行评估。

这是Scala语义。 val是一个不变的引用,它在声明站点被评估一次。 def代表方法定义,如果在其中定义了一个新的DataFrame,则每次调用它时都会创建一个。例如:

def ones = 
  df
   .schema
   .map(c => c.name)
   .drop(1)
   .map(x => when(col(x) === 1, 1).otherwise(0))
   .reduce(_ + _)

val firstcall = ones
val secondCall = ones

上面的代码将在DF上构建两个独立的DAG。

  

我假设使用val还是def都没关系   在这里,因为它本质上是一个循环,并且有一个减少。这是   对吗?

我不确定您在谈论哪个循环,但是请参阅上面的答案,以了解两者之间的区别。

  

是否会为每个数据帧行执行df.schema.map(c => c.name).drop(1)?   当然没有必要了。 Catalyst是否可以对此进行优化?

否,drop(1)将在整个数据帧中发生,这实际上将使它仅丢弃第一行。

  

如果以上情况成立,则每次执行该语句   要处理的列,如何使那段代码发生   一旦?我们是否应该让val的val = df.schema.map(c =>   c.name).drop(1)

每个数据帧仅发生一次(在您的示例中,它恰好是其中之一)。

答案 1 :(得分:1)

ones表达式不会在每个数据帧行中被求值,它将被求值一次。每个通话都会评估def个get。例如,如果有3个dataframe使用该ones表达式,则ones表达式将被评估3次。 val之间的区别在于,该表达式将只计算一次。

基本上,ones表达式创建org.apache.spark.sql.Column的实例,其中org.apache.spark.sql.Column = (CASE WHEN (a = 1) THEN 1 ELSE 0 END + CASE WHEN (b = 1) THEN 1 ELSE 0 END)。如果表达式是def,则每次调用新的org.apache.spark.sql.Column时都会实例化。如果表达式是val,则一遍又一遍地使用相同的实例。