First things first - unionAll is deprecated as of version 2 of Spark. Please use union instead, as you did in the second snippet.
Second, in the answers to the question, you're referencing to, there is no information, that the first piece of code is better. I have prepared such a scenario. For me, the first one took 31s and the second 18s. In my case df1 has ~3 million rows and df2 ~ 1 million, 5 columns each.
If we now analyse the optimized logical execution plan for the first query:
== 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
We can see, that there is Union and Join(intersection) running concurrently, which is very costly, especially Union, whereas for the second query:
== 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
There are two LeftAnti running concurrently (relative compliments). This takes less space and is more efficient. This can be seen in SparkUI:
First query:
Second query:

In the first case, stage 7 - Union is the most costly, whereas in the second case stages 42 and 41 (above) are relatively faster.