Spark SQL是否使用重复的表达式优化查询?

时间:2017-12-13 13:45:09

标签: apache-spark pyspark apache-spark-sql pyspark-sql

鉴于以下内容

from pyspark.sql import functions, window

f = functions.rank()
w1 = window.Window.partitionBy("column")
w2 = window.Window.partitionBy("column")
col = functions.col("column * 42")

和数据框df

的性能有任何差异
df.select(f.over(w1), f.over(w2))

VS

df.select(f.over(w1), f.over(w1))

怎么样?
df.select(col + 1, col + 2)

VS

df.select(functions.expr("column * 42 + 1"), functions.expr("column * 42 + 2")

(可以随意设想任意复杂的表达来代替column * 42

即。重用Column-和Window-instance与动态构建这些表达式有什么好处?

我希望Spark SQL能够正确地优化它,但无法找到答案。

另外,我是否应该通过检查df.explain()的结果来自己回答这个问题,如果是的话,我应该寻找什么?

1 个答案:

答案 0 :(得分:2)

  

随意想象任意复杂的表达式代替列* 42

...甚至任何非确定性表达式,例如生成随机数或当前时间戳。

每当你提出这样的问题时,使用explain运算符来查看Spark SQL处理的内容(实际上应该与编程语言和使用的函数或方法无关,不应该使用它) ?)

那么,在下面的非确定性查询的涵盖下会发生什么(或者是完全确定性的,但是乍一看是非确定性的):

val q = spark.range(1)
 .select(
   current_timestamp as "now",  // <-- this should be the same as the following line?
   current_timestamp as "now_2",
   rand as "r1", // <-- what about this and the following lines?
   rand as "r2",
   rand as "r3")
scala> q.show(truncate = false)
+-----------------------+-----------------------+-------------------+------------------+------------------+
|now                    |now_2                  |r1                 |r2                |r3                |
+-----------------------+-----------------------+-------------------+------------------+------------------+
|2017-12-13 15:17:46.305|2017-12-13 15:17:46.305|0.33579358107333823|0.9478025260069644|0.5846726225651472|
+-----------------------+-----------------------+-------------------+------------------+------------------+

我注意到rand所有产生了不同的结果实际上有点惊讶,因为我假设结果是相同的。答案是...... rand的源代码,如果没有明确定义,你可以看到它使用不同的种子(今天就学会了!谢谢)。

def rand(): Column = rand(Utils.random.nextLong)

答案是使用带有明确rand的{​​{1}}版本,因为它会在整个查询中为您提供相同seed逻辑运算符。{ / p>

Rand

Spark SQL知道你在结构化查询中使用了什么,因为名为seedval seed = 1 val q = spark.range(1) .select( current_timestamp as "now", // <-- this should be the same as the following line? current_timestamp as "now_2", rand(seed) as "r1", // <-- what about this and the following lines? rand(seed) as "r2", rand(seed) as "r3") scala> q.show(false) +-----------------------+-----------------------+-------------------+-------------------+-------------------+ |now |now_2 |r1 |r2 |r3 | +-----------------------+-----------------------+-------------------+-------------------+-------------------+ |2017-12-13 15:43:59.019|2017-12-13 15:43:59.019|0.06498948189958098|0.06498948189958098|0.06498948189958098| +-----------------------+-----------------------+-------------------+-------------------+-------------------+ 的Spark SQL的高级API只是围绕语言相同的逻辑运算符的包装(Python,Scala) ,Java,R,SQL)。

只需查看任何函数的源代码,您将看到Catalyst表达式(例如rand)或数据集运算符(例如select),您将看到一个或一个逻辑运算符树

最后,Spark SQL使用基于规则的优化器,该优化器使用规则来优化查询并查找重复。

所以,让我们来看看你的情况(比DataFrame更具确定性。)

(我使用的是Scala,但区别在于语言而非优化级别)

Dataset

在您的情况下,您使用了需要订购数据集的rand,因此我添加了import org.apache.spark.sql.expressions.Window val w1 = Window.partitionBy("column").orderBy("column") val w2 = Window.partitionBy("column").orderBy("column") 子句以使窗口规范完整。

rank

它们确实与Scala的观点不同

orderBy

使用数据集(这与我们的讨论几乎无关),让我们创建一个结构化查询并scala> w1 == w2 res1: Boolean = false 来查看Spark SQL执行的物理计划。

val df = spark.range(5).withColumnRenamed("id", "column")
scala> df.show
+------+
|column|
+------+
|     0|
|     1|
|     2|
|     3|
|     4|
+------+

让我们使用编号输出,以便我们可以参考说明中的每一行。

explain

通过它,你可以看到查询是否与另一个相似,如果有的话有什么不同。这是你可以得到的最明确的答案......惊喜...... Spark版本之间可能(并且经常会)发生变化。

  

即。重用Column-和Window-instance与动态构建这些表达式有什么好处?

我不会想太多,因为我希望Spark在内部处理它(而且你可能已经注意到我很惊讶地发现val q = df.select(rank over w1, rank over w2) scala> q.explain == Physical Plan == *Project [RANK() OVER (PARTITION BY column ORDER BY column ASC NULLS FIRST unspecifiedframe$())#193, RANK() OVER (PARTITION BY column ORDER BY column ASC NULLS FIRST unspecifiedframe$())#194] +- Window [rank(column#156L) windowspecdefinition(column#156L, column#156L ASC NULLS FIRST, specifiedwindowframe(RowFrame, unboundedpreceding$(), currentrow$())) AS RANK() OVER (PARTITION BY column ORDER BY column ASC NULLS FIRST unspecifiedframe$())#193, rank(column#156L) windowspecdefinition(column#156L, column#156L ASC NULLS FIRST, specifiedwindowframe(RowFrame, unboundedpreceding$(), currentrow$())) AS RANK() OVER (PARTITION BY column ORDER BY column ASC NULLS FIRST unspecifiedframe$())#194], [column#156L], [column#156L ASC NULLS FIRST] +- *Sort [column#156L ASC NULLS FIRST, column#156L ASC NULLS FIRST], false, 0 +- Exchange hashpartitioning(column#156L, 200) +- *Project [id#153L AS column#156L] +- *Range (0, 5, step=1, splits=8) 的工作原理不同)。

只需使用val plan = q.queryExecution.executedPlan scala> println(plan.numberedTreeString) 00 *Project [RANK() OVER (PARTITION BY column ORDER BY column ASC NULLS FIRST unspecifiedframe$())#193, RANK() OVER (PARTITION BY column ORDER BY column ASC NULLS FIRST unspecifiedframe$())#194] 01 +- Window [rank(column#156L) windowspecdefinition(column#156L, column#156L ASC NULLS FIRST, specifiedwindowframe(RowFrame, unboundedpreceding$(), currentrow$())) AS RANK() OVER (PARTITION BY column ORDER BY column ASC NULLS FIRST unspecifiedframe$())#193, rank(column#156L) windowspecdefinition(column#156L, column#156L ASC NULLS FIRST, specifiedwindowframe(RowFrame, unboundedpreceding$(), currentrow$())) AS RANK() OVER (PARTITION BY column ORDER BY column ASC NULLS FIRST unspecifiedframe$())#194], [column#156L], [column#156L ASC NULLS FIRST] 02 +- *Sort [column#156L ASC NULLS FIRST, column#156L ASC NULLS FIRST], false, 0 03 +- Exchange hashpartitioning(column#156L, 200) 04 +- *Project [id#153L AS column#156L] 05 +- *Range (0, 5, step=1, splits=8) 查看实际计划,您就可以自己回答问题。