-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Batch join lookup source #13352
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
Batch join lookup source #13352
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ | |
| import java.util.Arrays; | ||
| import java.util.List; | ||
|
|
||
| import static com.google.common.base.Preconditions.checkArgument; | ||
| import static io.airlift.slice.SizeOf.sizeOf; | ||
| import static io.airlift.units.DataSize.Unit.KILOBYTE; | ||
| import static io.trino.operator.SyntheticAddress.decodePosition; | ||
|
|
@@ -179,6 +180,78 @@ public int getAddressIndex(int position, Page hashChannelsPage) | |
| return -1; | ||
| } | ||
|
|
||
| @Override | ||
| public int[] getAddressIndex(int[] positions, Page hashChannelsPage, long[] rawHashes) | ||
| { | ||
| return getAddressIndex(positions, hashChannelsPage); | ||
| } | ||
|
|
||
| @Override | ||
| public int[] getAddressIndex(int[] positions, Page hashChannelsPage) | ||
| { | ||
| checkArgument(hashChannelsPage.getChannelCount() == 1, "Multiple channel page passed to BigintPagesHash"); | ||
|
|
||
| int positionCount = positions.length; | ||
| long[] incomingValues = new long[positionCount]; | ||
| int[] hashPositions = new int[positionCount]; | ||
|
|
||
| for (int i = 0; i < positionCount; i++) { | ||
| incomingValues[i] = hashChannelsPage.getBlock(0).getLong(positions[i], 0); | ||
| hashPositions[i] = getHashPosition(incomingValues[i], mask); | ||
| } | ||
|
|
||
| int[] found = new int[positionCount]; | ||
| int foundCount = 0; | ||
| int[] result = new int[positionCount]; | ||
| Arrays.fill(result, -1); | ||
| int[] foundKeys = new int[positionCount]; | ||
|
|
||
| // Search for positions in the hash array. This is the most CPU-consuming part as | ||
| // it relies on random memory accesses | ||
| for (int i = 0; i < positionCount; i++) { | ||
| foundKeys[i] = keys[hashPositions[i]]; | ||
| } | ||
| // Found positions are put into `found` array | ||
| for (int i = 0; i < positionCount; i++) { | ||
| if (foundKeys[i] != -1) { | ||
| found[foundCount++] = i; | ||
| } | ||
| } | ||
|
|
||
| // At this step we determine if the found keys were indeed the proper ones or it is a hash collision. | ||
| // The result array is updated for the found ones, while the collisions land into `remaining` array. | ||
| int[] remaining = found; // Rename for readability | ||
| int remainingCount = 0; | ||
|
|
||
| for (int i = 0; i < foundCount; i++) { | ||
| int index = found[i]; | ||
| if (values[foundKeys[index]] == incomingValues[index]) { | ||
| result[index] = foundKeys[index]; | ||
| } | ||
| else { | ||
| remaining[remainingCount++] = index; | ||
| } | ||
| } | ||
|
|
||
| // At this point for any reasoable load factor of a hash array (< .75), there is no more than | ||
| // 10 - 15% of positions left. We search for them in a sequential order and update the result array. | ||
| for (int i = 0; i < remainingCount; i++) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you think it's a good idea to extract this part to a separate method? it would be useful to see the impact of this part in the profiler output
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will get inlined anyway. As of readability, I prefer those perf-optimised methods to be a bit longer but with some comments. I believe it is more readable that the standard clean code approach, given that the code is not easy to understand and never will be.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Probably, but profilers can figure out the original method in most cases and attribute the time correctly e.g. in the flame graph.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WDYM? If the method is inlined it does not exist in the JFR. Or am I missing something? |
||
| int index = remaining[i]; | ||
| int position = (hashPositions[index] + 1) & mask; // hashPositions[index] position has already been checked | ||
|
|
||
| while (keys[position] != -1) { | ||
| if (values[keys[position]] == incomingValues[index]) { | ||
raunaqmorarka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| result[index] = keys[position]; | ||
| break; | ||
| } | ||
| // increment position and mask to handler wrap around | ||
| position = (position + 1) & mask; | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| @Override | ||
| public void appendTo(long position, PageBuilder pageBuilder, int outputChannelOffset) | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -175,6 +175,78 @@ public int getAddressIndex(int rightPosition, Page hashChannelsPage, long rawHas | |
| return -1; | ||
| } | ||
|
|
||
| @Override | ||
| public int[] getAddressIndex(int[] positions, Page hashChannelsPage) | ||
| { | ||
| long[] hashes = new long[positions[positions.length - 1] + 1]; | ||
| for (int i = 0; i < positions.length; i++) { | ||
| hashes[positions[i]] = pagesHashStrategy.hashRow(positions[i], hashChannelsPage); | ||
| } | ||
|
|
||
| return getAddressIndex(positions, hashChannelsPage, hashes); | ||
| } | ||
|
|
||
| @Override | ||
| public int[] getAddressIndex(int[] positions, Page hashChannelsPage, long[] rawHashes) | ||
| { | ||
| int positionCount = positions.length; | ||
| int[] hashPositions = new int[positionCount]; | ||
|
|
||
| for (int i = 0; i < positionCount; i++) { | ||
| hashPositions[i] = getHashPosition(rawHashes[positions[i]], mask); | ||
| } | ||
|
|
||
| int[] found = new int[positionCount]; | ||
| int foundCount = 0; | ||
| int[] result = new int[positionCount]; | ||
| Arrays.fill(result, -1); | ||
| int[] foundKeys = new int[positionCount]; | ||
|
|
||
| // Search for positions in the hash array. This is the most CPU-consuming part as | ||
| // it relies on random memory accesses | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. Batching some methods from |
||
| for (int i = 0; i < positionCount; i++) { | ||
| foundKeys[i] = keys[hashPositions[i]]; | ||
| } | ||
| // Found positions are put into `found` array | ||
| for (int i = 0; i < positionCount; i++) { | ||
| if (foundKeys[i] != -1) { | ||
| found[foundCount++] = i; | ||
| } | ||
| } | ||
|
|
||
| // At this step we determine if the found keys were indeed the proper ones or it is a hash collision. | ||
| // The result array is updated for the found ones, while the collisions land into `remaining` array. | ||
| int[] remaining = found; // Rename for readability | ||
| int remainingCount = 0; | ||
| for (int i = 0; i < foundCount; i++) { | ||
| int index = found[i]; | ||
| if (positionEqualsCurrentRowIgnoreNulls(foundKeys[index], (byte) rawHashes[positions[index]], positions[index], hashChannelsPage)) { | ||
| result[index] = foundKeys[index]; | ||
| } | ||
| else { | ||
| remaining[remainingCount++] = index; | ||
| } | ||
| } | ||
|
|
||
| // At this point for any reasoable load factor of a hash array (< .75), there is no more than | ||
| // 10 - 15% of positions left. We search for them in a sequential order and update the result array. | ||
| for (int i = 0; i < remainingCount; i++) { | ||
| int index = remaining[i]; | ||
| int position = (hashPositions[index] + 1) & mask; // hashPositions[index] position has already been checked | ||
|
|
||
| while (keys[position] != -1) { | ||
| if (positionEqualsCurrentRowIgnoreNulls(keys[position], (byte) rawHashes[positions[index]], positions[index], hashChannelsPage)) { | ||
| result[index] = keys[position]; | ||
| break; | ||
| } | ||
| // increment position and mask to handler wrap around | ||
| position = (position + 1) & mask; | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| @Override | ||
| public void appendTo(long position, PageBuilder pageBuilder, int outputChannelOffset) | ||
| { | ||
|
|
||
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.
there is also
values[foundKeys[index]]at line 228 that is random load, right?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.
Correct. But it only fetches positions that has been found, which is often only a fraction.