- 
                Notifications
    You must be signed in to change notification settings 
- Fork 28.9k
[SPARK-29008][SQL] Define an individual method for each common subexpression in HashAggregateExec #25710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| Test build #110237 has finished for PR 25710 at commit  
 | 
| Test build #110238 has finished for PR 25710 at commit  
 | 
| retest this please | 
| Test build #110251 has finished for PR 25710 at commit  
 | 
| } | ||
| } | ||
|  | ||
| val codes = if (commonExprVals.map(_.code.length).sum > SQLConf.get.methodSplitThreshold) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although the original method should contain not only common expressions, this is probably good enough.
| 
 I also ran this benchmark to verify, but seems current master doesn't take so long on my laptop. It tooks about 4-5s. Is anything causing the difference? | 
| 
 oh....probably, I made some mistakes.... I will re-run it and update the description later. Anyway, thanks for your check! | 
| also cc: @cloud-fan @rednaxelafx @mgaido91 | 
| JavaCode.isNullGlobal(isNull), JavaCode.global(value, expr.dataType)) | ||
| exprs.foreach(localSubExprEliminationExprs.put(_, state)) | ||
| val inputVariables = inputVars.map(_.variableName).mkString(", ") | ||
| s"${addNewFunction(fnName, fn)}($inputVariables);" | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| I haven't reviewed this PR in detail yet, just some first thoughts: At a glance I'm neutral about this PR. In general I don't like code splitting that cause premature spilling of state from locals to fields. I might be more in favor of: long commonSubExpr0 = agg_subExpr_0(input1, input2);
agg_doAggregate_sum_0(commonSubExpr0);
...than agg_subExpr_0(input1, input2); // result goes to this.commonSubExpr0
agg_doAggregate_sum_0(); // argument passed through `this`
...In practice, after thorough inlining, the performance shouldn't be too different, but I just don't like the idea of blindly spilling state to fields when it's not necessary. | 
| I agree with @rednaxelafx . Introducing many class fields shouldn't probably cause issues with the constant pool since we can batch variables in arrays, but this is also suboptimal. So it'd be great if we could avoid that. | 
| Yea, I think so, too. If its possible for a split function to return two variables ( | 
| What about leaving global only the  | 
| Ah, that's one of choices. I'll try to brush up the code based on that. Thanks! | 
| | $isNullEvalCode | ||
| | return ${eval.value}; | ||
| |} | ||
| """.stripMargin | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ISTM we might be able to apply the same change in https://github.com/apache/spark/blob/master/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/codegen/CodeGenerator.scala#L1060-L1069
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea, we can do it in a followup.
| """.stripMargin | ||
|  | ||
| val value = freshName("subExprValue") | ||
| val state = SubExprEliminationState(isNull, JavaCode.variable(value, expr.dataType)) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One advantage of global variable is we don't care how this expr value is used later. It is ok even it is used in a split function. It is a local variable means we need to be careful and guarantee that these expressions would only be used at same scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I see. But, I just want add more pressure on the constant pool.... WDYT? @cloud-fan
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK the code of common subexpression execution is always put together, not split. I don't think we need to worry about it now.
BTW I think one principle is: for corner cases which are really hard to generate code, we should just fallback to interpreted mode.
| Test build #110295 has finished for PR 25710 at commit  
 | 
| retest this please | 
| Test build #110301 has finished for PR 25710 at commit  
 | 
| LGTM. As usual, can we have a simple microbenchmark to show the advantage? I saw some discussion about the perf numbers but I can't find it in the PR description. | 
| oh... I forgot to re-benchmark that. (I put wrong benchmark numbers first, so I removed then). I'll run benchmarks again and update the description for that soon. | 
| I updated the PR description;  | 
| ping @cloud-fan @viirya | 
| retest this please | 
| Test build #110495 has finished for PR 25710 at commit  
 | 
|  | ||
| test("Give up splitting subexpression code if a parameter length goes over the limit") { | ||
| withSQLConf( | ||
| SQLConf.CODEGEN_SPLIT_AGGREGATE_FUNC.key -> "false", | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test must be run under CODEGEN_SPLIT_AGGREGATE_FUNC = false?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, we need to. If that flag is true, HashAggregateExec throws an exception in this test: https://github.com/apache/spark/blob/master/sql/core/src/main/scala/org/apache/spark/sql/execution/aggregate/HashAggregateExec.scala#L327
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. One question about test.
| LGTM, cc @rednaxelafx to take another look | 
| ping @rednaxelafx | 
| retest this please | 
| Test build #110578 has finished for PR 25710 at commit  
 | 
| Thanks! Merged to master. | 
What changes were proposed in this pull request?
This pr proposes to define an individual method for each common subexpression in HashAggregateExec. In the current master, the common subexpr elimination code in HashAggregateExec is expanded in a single method;
spark/sql/core/src/main/scala/org/apache/spark/sql/execution/aggregate/HashAggregateExec.scala
Line 397 in 4664a08
The method size can be too big for JIT compilation, so I believe splitting it is beneficial for performance. For example, in a query
SELECT SUM(a + b), AVG(a + b + c) FROM VALUES (1, 1, 1) t(a, b, c),the current master generates;
On the other hand, this pr generates;
I run some micro benchmarks for this pr;
Why are the changes needed?
To avoid the too-long-function issue in JVMs.
Does this PR introduce any user-facing change?
No.
How was this patch tested?
Added tests in
WholeStageCodegenSuite