Max Gekk created SPARK-59765:
--------------------------------

             Summary: Codegen size checks over empty TPC-DS tables measure 
stubs for broadcast joins
                 Key: SPARK-59765
                 URL: https://issues.apache.org/jira/browse/SPARK-59765
             Project: Spark
          Issue Type: Test
          Components: SQL
    Affects Versions: 5.0.0
            Reporter: Max Gekk


Two instruments measure the code whole-stage codegen generates for the TPC-DS 
queries without running them: {{BenchmarkQueryTest.checkGeneratedCode}}, used 
by {{TPCDSQuerySuite}} and its variants (see SPARK-59764), and 
{{WholeStageCodegenSizeBenchmark}} (SPARK-57915), the instrument for the 
SPARK-56908 umbrella. Both plan each query over empty TPC-DS tables with AQE 
disabled, and call {{doCodeGen()}} on every {{WholeStageCodegenExec}}.

With AQE disabled, {{BroadcastHashJoinExec.prepareBroadcast}} executes the 
broadcast while generating code. Over an empty table the relation is 
{{EmptyHashedRelation}}, and {{HashJoin}} then generates a one-line comment in 
place of the join: "If HashedRelation is empty, hash inner join simply returns 
nothing" (and the semi join equivalent), in which case the code of every 
operator after the join in the stage is not generated either. For an anti join 
it generates only the pass-through. {{BroadcastNestedLoopJoinExec}} has a 
similar shortcut for an empty build side. So a stage with a broadcast join is 
measured as a stub, and its method sizes and constant pools are understated.

How much, on master (4cfbd411966): with {{WholeStageCodegenSizeBenchmark}}'s 
setup (injected SF100 statistics with CBO, AQE off, empty tables), the 
generated code of 324 of the 2201 whole-stage stages of the 129 TPC-DS v1.4 and 
v2.7 queries contains the empty-relation stub, in 127 of the 129 queries. 
Measured by planning each query as {{TPCDSQueryWithStatsSuite}} does, taking 
{{codegenStringSeq(executedPlan)}}, and counting the stages whose source 
contains "If HashedRelation is empty".

In a fork close to master, generating those stages in full made 374 stages 
larger, the largest by about 3.5KB of bytecode for one method. So the 
benchmark's totals and the 8000-byte check both see less code than the queries 
compile over real data.

Proposed fix, for both: generate each stage's code from a copy of the stage in 
which every {{BroadcastExchangeExec}} (including one under a 
{{ReusedExchangeExec}}) reads a one-row {{LocalTableScanExec}} of non-null 
default values. The joins then generate the code they generate for data, and 
nothing else about the plan changes. Assert that no generated code still 
contains the empty-relation comment, so the stub cannot come back unnoticed. 
One approximation remains: a one-row build side makes every key unique, so a 
join generates its unique-key form; a join whose real build side repeats keys 
also has the loop over matches.

Populating the tables with data would also work, but a fixed row is filtered 
out by any literal predicate on the build side (for example {{d_year = 2000}}), 
which leaves the relation empty again.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to