-
Notifications
You must be signed in to change notification settings - Fork 28.9k
[SPARK-22746][SQL] Avoid the generation of useless mutable states by SortMergeJoin #19937
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
|
|
||
| s""" | ||
| |while (findNextInnerJoinRows($leftInput, $rightInput)) { | ||
| | ${leftVarDecl.mkString("\n")} |
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.
can't we define them before the loop and reuse them?
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.
It could be. Would it be possible to let us know advantages compare to the current method?
I think that to shorten lifetime of variables (i.e. current approach) makes generated code more readable.
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.
Since I have no strong preference, I would like to hear opinions from others.
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 think that the advantage would be to reuse them, avoiding creating and destroying them at every loop.
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.
Since they are local variable, it takes almost no cost in native code. If they are on CPU registers, there is no cost. If they are in stack frame, it is up to one instruction to increase or decrease stack frame size.
WDYT? Did you see huge overhead to create and destroy local variables?
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.
We would appreciate it if you would past the kernel code what you are thinking about.
This is because we would like to measure overhead which you are taking care of.
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.
Here is my benchmark result. Is there any your result?
$ cat Loop.java
public class Loop {
private static int _i;
private static boolean _b;
public static void main(String[] args){
Loop l = new Loop();
long s, e;
long c1 = 10000000000L;
long c2 = 100000L;
int N = 10;
// warmup
for (int i = 0; i < 1000000; i++) {
l.inner(10000, 100);
l.outer(10000, 100);
}
for (int n = 0; n < N; n++) {
s = System.nanoTime();
l.inner(c1, c2);
e = System.nanoTime();
System.out.println("inner(ms): " + (e-s) / 1000000);
}
for (int n = 0; n < N; n++) {
s = System.nanoTime();
l.outer(c1, c2);
e = System.nanoTime();
System.out.println("outer(ms): " + (e-s) / 1000000);
}
}
public void inner(long c1, long c2) {
for (long i = 0; i < c1; i++) {
int v1 = 1;
boolean v2 = true;
for (long j = 0; i < c2; i++) {
_i = v1;
_b = v2;
}
}
}
public void outer(long c1, long c2) {
int v1 = 1;
boolean v2 = true;
for (long i = 0; i < c1; i++) {
for (long j = 0; i < c2; i++) {
_i = v1;
_b = v2;
}
}
}
}
$ java -version
openjdk version "1.8.0_131"
OpenJDK Runtime Environment (build 1.8.0_131-8u131-b11-2ubuntu1.16.04.3-b11)
OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)
$ java Loop
inner(ms): 2779
inner(ms): 2779
inner(ms): 2779
inner(ms): 2779
inner(ms): 2779
inner(ms): 2779
inner(ms): 2779
inner(ms): 2779
inner(ms): 2779
inner(ms): 2779
outer(ms): 2779
outer(ms): 2779
outer(ms): 2779
outer(ms): 2779
outer(ms): 2779
outer(ms): 2779
outer(ms): 2779
outer(ms): 2779
outer(ms): 2779
outer(ms): 2779
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.
If no performance difference, we prefer to the simpler codes.
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.
Since these local variables are only needed inside the loop, I feel the current code is more readable. Performance is not a concern here as the overhead is very low or none.
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.
@kiszk thanks for the test. Then I agree this is the best option, thanks.
|
Test build #84685 has finished for PR 19937 at commit
|
|
@cloud-fan @viirya Could you please review this? |
| * codegen of BoundReference here. | ||
| */ | ||
| private def createLeftVars(ctx: CodegenContext, leftRow: String): Seq[ExprCode] = { | ||
| private def createLeftVars(ctx: CodegenContext, leftRow: String): (Seq[ExprCode], Seq[String]) = { |
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.
We need to update the function description.
| s""" | ||
| |boolean $isNull = false; | ||
| |${ctx.javaType(a.dataType)} $value = ${ctx.defaultValue(a.dataType)}; | ||
| """.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.
Could you assign it first
val leftVarsDecl =
...
(ExprCode(code, isNull, value), leftVarsDecl)
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.
+1
| } else { | ||
| ExprCode(s"$value = $valueCode;", "false", value) | ||
| (ExprCode(s"$value = $valueCode;", "false", value), | ||
| s"""${ctx.javaType(a.dataType)} $value = ${ctx.defaultValue(a.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.
The same here.
|
LGTM except one code style comment |
| |$value = $isNull ? ${ctx.defaultValue(a.dataType)} : ($valueCode); | ||
| """.stripMargin | ||
| ExprCode(code, isNull, value) | ||
| (ExprCode(code, isNull, value), |
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.
Why separate variable declaration and code to two parts? Previously it is because it uses global variables. Now since we use local variables, I think we can simply do:
val code =
s"""
|boolean $isNull = $leftRow.isNullAt($i);
|${ctx.javaType(a.dataType)} $value = $isNull ? ${ctx.defaultValue(a.dataType)} : ($valueCode);
""".stripMarginDon't we?
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.
good catch! Makes sense to me.
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.
Good question. If we declare variable types only at code, it may lead to compilation error injanino.
Around here, the code that you pointed out may go to $leftAfter in $condCheck that exists in a if-then block at the inner most of the loop in the generated code, by using leftVar. The variable in leftVars is also referred to at ${consume(ctx, leftVars ++ rightVars)}.
In the following example, If we declare variable types only at code, we will drop lines 145 and will declare int for smj_value8 at lines 162. Since smj_value8 is refered at line 169, the generated code would cause compilation error.
WDYT?
/* 143 */ protected void processNext() throws java.io.IOException {
/* 144 */ while (findNextInnerJoinRows(smj_leftInput, smj_rightInput)) {
/* 145 */ int smj_value8 = -1;
/* 146 */ boolean smj_isNull6 = false;
/* 147 */ int smj_value9 = -1;
/* 148 */ boolean smj_loaded = false;
/* 149 */ smj_isNull6 = smj_leftRow.isNullAt(1);
/* 150 */ smj_value9 = smj_isNull6 ? -1 : (smj_leftRow.getInt(1));
/* 151 */ scala.collection.Iterator<UnsafeRow> smj_iterator = smj_matches.generateIterator();
/* 152 */ while (smj_iterator.hasNext()) {
...
/* 160 */ if (!smj_loaded) {
/* 161 */ smj_loaded = true;
/* 162 */ smj_value8 = smj_leftRow.getInt(0);
/* 163 */ }
/* 164 */ int smj_value10 = smj_rightRow1.getInt(0);
/* 165 */ smj_numOutputRows.add(1);
/* 166 */
/* 167 */ smj_rowWriter.zeroOutNullBytes();
/* 168 */
/* 169 */ smj_rowWriter.write(0, smj_value8);
...
/* 185 */
/* 186 */ }
/* 187 */ if (shouldStop()) return;
/* 188 */ }
/* 189 */ }
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, I just ran the test and found it too. Looks like puts the declaration at top is simplest.
|
LGTM |
|
LGTM too |
|
Test build #84703 has finished for PR 19937 at commit
|
|
retest this please |
|
Test build #84705 has finished for PR 19937 at commit
|
|
retest this please |
|
Test build #84718 has finished for PR 19937 at commit
|
|
Jenkins, retest this please |
|
The test failure is not related to this PR. I have reverted the PR that caused the CRAN checking failure. |
|
LGTM Thanks! Merged to master |
|
Test build #84723 has finished for PR 19937 at commit
|
|
Thank you for pointing out the root cause of this failure. |
What changes were proposed in this pull request?
This PR reduce the number of global mutable variables in generated code of
SortMergeJoin.Before this PR, global mutable variables are used to extend lifetime of variables in the nested loop. This can be achieved by declaring variable at the outer most loop level where the variables are used.
In the following example,
smj_value8,smj_value8, andsmj_value9are declared as local variable at lines 145-147 inWith this PR.This PR fixes potential assertion error by #19865. Without this PR, a global mutable variable is potentially passed to arguments in generated code of split function.
Without this PR
With this PR
How was this patch tested?
Existing test cases