当我用distinct()
替换spark数据帧上的groupBy()
时,我的pyspark代码获得了很多性能改进。但是我不明白背后的原因。
整个目的是从数据框中删除行级重复项。
我尝试在pyspark中谷歌搜索groupBy()
和distinct()
的实现,但是找不到它。
有人可以向我解释或指出正确的方向吗?
答案 0 :(得分:0)
distinct()实现检查每一列,如果两行或更多行完全相同,则保留第一行。 我认为这是主要原因,为什么区别如此之慢。
答案 1 :(得分:0)
我最近专注于 Apache Spark SQL 中 GROUP BY
和 DISTINCT
操作之间的区别。碰巧...两者有时可能相同!
要看到这一点,请运行以下代码并检查执行计划:
(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 执行的计划?