比较Spark中的两个数据帧(性能)

时间:2019-01-07 15:35:39

标签: java scala performance apache-spark apache-spark-sql

我需要在我的spark应用程序中比较两个数据框。我经历了以下帖子。 How to obtain the difference between two DataFrames?

但是,我不明白为什么该方法是最佳答案

df1.unionAll(df2).except(df1.intersect(df2))

比问题中的那个要好

df1.except(df2).union(df2.except(df1))

有人可以解释吗? 根据我的理解,后者适用于两个较小的数据集,而前者适用于较大的数据集。是否因为后者作为工会的一部分而与众不同?即使这样,如果两个数据帧具有相同记录的可能性更大,那么在后一种情况下,我们将处理一个小的数据集。

2 个答案:

答案 0 :(得分:2)

首先要注意的是-从{Spark版本2开始,不推荐使用unionAll。请像在第二个片段中一样使用union

第二,在您所指问题的答案中,没有信息表明第一段代码更好。我已经准备好了这种情况。对我来说,第一个花费31s,第二个花费18s。在我的情况下,df1有约300万行,df2有约100万,每行5列。

如果我们现在为第一个查询分析优化的逻辑执行计划:

== Optimized Logical Plan ==
GlobalLimit 21
+- LocalLimit 21
   +- Aggregate [_c0#10, _c1#11, _c2#12, _c3#13, _c4#14], [cast(_c0#10 as string) AS _c0#67, cast(_c1#11 as string) AS _c1#68, cast(_c2#12 as string) AS _c2#69, cast(_c3#13 as string) AS _c3#70, cast(_c4#14 as string) AS _c4#71]
      +- Join LeftAnti, (((((_c0#10 <=> _c0#52) && (_c1#11 <=> _c1#53)) && (_c2#12 <=> _c2#54)) && (_c3#13 <=> _c3#55)) && (_c4#14 <=> _c4#56))
         :- Union
         :  :- Relation[_c0#10,_c1#11,_c2#12,_c3#13,_c4#14] csv
         :  +- Project [_c0#30, _c1#31, _c2#32, _c3#33, cast(_c4#34 as double) AS _c4#40]
         :     +- Relation[_c0#30,_c1#31,_c2#32,_c3#33,_c4#34] csv
         +- Aggregate [_c0#52, _c1#53, _c2#54, _c3#55, _c4#56], [_c0#52, _c1#53, _c2#54, _c3#55, _c4#56]
            +- Join LeftSemi, (((((_c0#52 <=> _c0#30) && (_c1#53 <=> _c1#31)) && (_c2#54 <=> _c2#32)) && (_c3#55 <=> _c3#33)) && (_c4#56 <=> _c4#46))
               :- Relation[_c0#52,_c1#53,_c2#54,_c3#55,_c4#56] csv
               +- Project [_c0#30, _c1#31, _c2#32, _c3#33, cast(_c4#34 as double) AS _c4#46]
                  +- Relation[_c0#30,_c1#31,_c2#32,_c3#33,_c4#34] csv

我们可以看到,UnionJoin(交叉点)同时运行,这是非常昂贵的,尤其是Union,而对于第二个查询:

== Optimized Logical Plan ==
GlobalLimit 21
+- LocalLimit 21
   +- Union
      :- LocalLimit 21
      :  +- Aggregate [_c0#10, _c1#11, _c2#12, _c3#13, _c4#14], [cast(_c0#10 as string) AS _c0#120, cast(_c1#11 as string) AS _c1#121, cast(_c2#12 as string) AS _c2#122, cast(_c3#13 as string) AS _c3#123, cast(_c4#14 as string) AS _c4#124]
      :     +- Join LeftAnti, (((((_c0#10 <=> _c0#30) && (_c1#11 <=> _c1#31)) && (_c2#12 <=> _c2#32)) && (_c3#13 <=> _c3#33)) && (_c4#14 <=> _c4#98))
      :        :- Relation[_c0#10,_c1#11,_c2#12,_c3#13,_c4#14] csv
      :        +- Project [_c0#30, _c1#31, _c2#32, _c3#33, cast(_c4#34 as double) AS _c4#98]
      :           +- Relation[_c0#30,_c1#31,_c2#32,_c3#33,_c4#34] csv
      +- LocalLimit 21
         +- Aggregate [_c0#30, _c1#31, _c2#32, _c3#33, _c4#104], [cast(_c0#30 as string) AS _c0#130, cast(_c1#31 as string) AS _c1#131, cast(_c2#32 as string) AS _c2#132, cast(_c3#33 as string) AS _c3#133, cast(_c4#104 as string) AS _c4#134]
            +- Join LeftAnti, (((((_c0#30 <=> _c0#10) && (_c1#31 <=> _c1#11)) && (_c2#32 <=> _c2#12)) && (_c3#33 <=> _c3#13)) && (_c4#104 <=> _c4#14))
               :- Project [_c0#30, _c1#31, _c2#32, _c3#33, cast(_c4#34 as double) AS _c4#104]
               :  +- Relation[_c0#30,_c1#31,_c2#32,_c3#33,_c4#34] csv
               +- Relation[_c0#10,_c1#11,_c2#12,_c3#13,_c4#14] csv

有两个LeftAnti同时运行(相对称赞)。这样占用的空间更少,效率更高。这可以在SparkUI中看到:

第一个查询: First query 第二个查询: Second query

在第一种情况下,阶段7-Union的成本最高,而在第二种情况下,阶段42和41(上述)相对较快。

答案 1 :(得分:2)

让我们考虑以下情形:df1df2(分别为N和M的大小)都太大而无法广播,但是df1和{{1之间没有重叠}}。

我们称其为结果df2。在这种情况下,di将需要N + M行的完整混洗,但是输出的大小将等于0。在这种情况下,df1.intersect(df2)可以作为广播联接执行(这种优化可能要求adaptive execution,除非用户强制执行特定计划)。同样重要的是要注意,这种计划不需要缓存。

相反,就相交的基数而言,df1.unionAll(df2).except(di)的成本将是恒定的。

同时,如果df1.except(df2).union(df2.except(df1))太大而无法广播,则它已经具有与d1兼容的分区,因此其余查询不需要额外的洗牌。