为什么在pyspark中,groupBy()比distinct()快很多?

时间:2018-09-11 10:49:15

标签: pyspark

当我用distinct()替换spark数据帧上的groupBy()时,我的pyspark代码获得了很多性能改进。但是我不明白背后的原因。 整个目的是从数据框中删除行级重复项。

我尝试在pyspark中谷歌搜索groupBy()distinct()的实现,但是找不到它。

有人可以向我解释或指出正确的方向吗?

2 个答案:

答案 0 :(得分:0)

distinct()实现检查每一列,如果两行或更多行完全相同,则保留第一行。 我认为这是主要原因,为什么区别如此之慢。

Check this topic too.

答案 1 :(得分:0)

我最近专注于 Apache Spark SQL 中 GROUP BYDISTINCT 操作之间的区别。碰巧...两者有时可能相同!

要看到这一点,请运行以下代码并检查执行计划:

(0 to 10).map(id => (s"id#${id}", s"login${id % 25}"))
   .toDF("id", "login").createTempView("users")

sparkSession.sql("SELECT login FROM users GROUP BY login").explain(true)
sparkSession.sql("SELECT DISTINCT(login) FROM users").explain(true)

惊喜,惊喜!计划应如下所示:

== Physical Plan ==
*(2) HashAggregate(keys=[login#8], functions=[], output=[login#8])
+- Exchange hashpartitioning(login#8, 200), ENSURE_REQUIREMENTS, [id=#33]
   +- *(1) HashAggregate(keys=[login#8], functions=[], output=[login#8])
      +- *(1) LocalTableScan [login#8]

为什么?由于 ReplaceDistinctWithAggregate 规则,您应该在日志中看到实际操作:

=== Applying Rule org.apache.spark.sql.catalyst.optimizer.ReplaceDistinctWithAggregate ===
!Distinct                     Aggregate [login#8], [login#8]
 +- LocalRelation [login#8]   +- LocalRelation [login#8]
           (org.apache.spark.sql.catalyst.rules.PlanChangeLogger:65)

============================ 更新:

对于更复杂的查询(例如使用聚合),可能会有所不同。

sparkSession.sql("SELECT COUNT(login) FROM users GROUP BY login").explain(true)
sparkSession.sql("SELECT COUNT(DISTINCT(login)) FROM users").explain(true)

GROUP BY 版本生成的计划只有一次 shuffle:

== Physical Plan ==
*(2) HashAggregate(keys=[login#8], functions=[count(login#8)], output=[count(login)#12L])
+- Exchange hashpartitioning(login#8, 200), ENSURE_REQUIREMENTS, [id=#16]
   +- *(1) HashAggregate(keys=[login#8], functions=[partial_count(login#8)], output=[login#8, count#15L])
      +- *(1) LocalTableScan [login#8]

而带有 DISTINCT 的版本会生成 2 次 shuffle。第一个是对登录进行重复数据删除,第二个是对它们进行计数:

== Physical Plan ==
*(3) HashAggregate(keys=[], functions=[count(distinct login#8)], output=[count(DISTINCT login)#17L])
+- Exchange SinglePartition, ENSURE_REQUIREMENTS, [id=#48]
   +- *(2) HashAggregate(keys=[], functions=[partial_count(distinct login#8)], output=[count#21L])
      +- *(2) HashAggregate(keys=[login#8], functions=[], output=[login#8])
         +- Exchange hashpartitioning(login#8, 200), ENSURE_REQUIREMENTS, [id=#43]
            +- *(1) HashAggregate(keys=[login#8], functions=[], output=[login#8])
               +- *(1) LocalTableScan [login#8]

然而,这些查询在语义上并不相同,因为第一个生成登录组,而第二个也计算它们。并且解释了额外的 shuffle 步骤。

在更改之前/之后使用代码回答问题可能会更容易。 @pri,您是否拥有它以便我们分析 PySpark 执行的计划?