下面的代码以及有关性能的问题-当然可以想象:
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-调用时进行评估。
对于-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)
答案 0 :(得分:5)
这里有两个核心概念,Spark DAG创建和评估,以及Scala的val
和def
定义,它们是正交的
我认为.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
,则一遍又一遍地使用相同的实例。