Skip to content

Commit 9e353e7

Browse files
committed
precalculate hashes & remove iterators
1 parent b631c1e commit 9e353e7

2 files changed

Lines changed: 72 additions & 43 deletions

File tree

datafusion/physical-plan/src/joins/hash_join.rs

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,15 +1039,14 @@ impl RecordBatchStream for HashJoinStream {
10391039
/// Probe indices: 3, 3, 4, 5
10401040
/// ```
10411041
#[allow(clippy::too_many_arguments)]
1042-
fn lookup_join_hashmap<T: JoinHashMapType>(
1043-
build_hashmap: &T,
1042+
fn lookup_join_hashmap(
1043+
build_hashmap: &JoinHashMap,
10441044
build_input_buffer: &RecordBatch,
10451045
probe_batch: &RecordBatch,
10461046
build_on: &[Column],
10471047
probe_on: &[Column],
1048-
random_state: &RandomState,
10491048
null_equals_null: bool,
1050-
hashes_buffer: &mut Vec<u64>,
1049+
hashes_buffer: &[u64],
10511050
limit: usize,
10521051
offset: JoinHashMapOffset,
10531052
) -> Result<(UInt64Array, UInt32Array, Option<JoinHashMapOffset>)> {
@@ -1063,17 +1062,8 @@ fn lookup_join_hashmap<T: JoinHashMapType>(
10631062
})
10641063
.collect::<Result<Vec<_>>>()?;
10651064

1066-
hashes_buffer.clear();
1067-
hashes_buffer.resize(probe_batch.num_rows(), 0);
1068-
let hash_values = create_hashes(&keys_values, random_state, hashes_buffer)?;
1069-
10701065
let (mut probe_builder, mut build_builder, next_offset) = build_hashmap
1071-
.get_matched_indices_with_limit_offset(
1072-
hash_values.iter().enumerate(),
1073-
None,
1074-
limit,
1075-
offset,
1076-
);
1066+
.get_matched_indices_with_limit_offset(hashes_buffer, None, limit, offset);
10771067

10781068
let build_indices: UInt64Array =
10791069
PrimitiveArray::new(build_builder.finish().into(), None);
@@ -1233,6 +1223,17 @@ impl HashJoinStream {
12331223
self.state = HashJoinStreamState::ExhaustedProbeSide;
12341224
}
12351225
Some(Ok(batch)) => {
1226+
// Precalculate hash values for fetched batch
1227+
let keys_values = self
1228+
.on_right
1229+
.iter()
1230+
.map(|c| c.evaluate(&batch)?.into_array(batch.num_rows()))
1231+
.collect::<Result<Vec<_>>>()?;
1232+
1233+
self.hashes_buffer.clear();
1234+
self.hashes_buffer.resize(batch.num_rows(), 0);
1235+
create_hashes(&keys_values, &self.random_state, &mut self.hashes_buffer)?;
1236+
12361237
self.state =
12371238
HashJoinStreamState::ProcessProbeBatch(ProcessProbeBatchState {
12381239
batch,
@@ -1266,9 +1267,8 @@ impl HashJoinStream {
12661267
&state.batch,
12671268
&self.on_left,
12681269
&self.on_right,
1269-
&self.random_state,
12701270
self.null_equals_null,
1271-
&mut self.hashes_buffer,
1271+
&self.hashes_buffer,
12721272
self.batch_size,
12731273
state.offset,
12741274
)?;
@@ -2935,18 +2935,24 @@ mod tests {
29352935
("c", &vec![30, 40]),
29362936
);
29372937

2938+
// Join key column for both join sides
2939+
let key_column = Column::new("a", 0);
2940+
29382941
let join_hash_map = JoinHashMap::new(hashmap_left, next);
2939-
let mut hashes_buffer = vec![0];
2942+
2943+
let right_keys_values =
2944+
key_column.evaluate(&right)?.into_array(right.num_rows())?;
2945+
let mut hashes_buffer = vec![0; right.num_rows()];
2946+
create_hashes(&[right_keys_values], &random_state, &mut hashes_buffer)?;
29402947

29412948
let (l, r, _) = lookup_join_hashmap(
29422949
&join_hash_map,
29432950
&left,
29442951
&right,
2945-
&[Column::new("a", 0)],
2946-
&[Column::new("a", 0)],
2947-
&random_state,
2952+
&[key_column.clone()],
2953+
&[key_column],
29482954
false,
2949-
&mut hashes_buffer,
2955+
&hashes_buffer,
29502956
8192,
29512957
(0, None),
29522958
)?;

datafusion/physical-plan/src/joins/utils.rs

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,9 @@ pub trait JoinHashMapType {
236236
///
237237
/// This method only compares hashes, so additional further check for actual values
238238
/// equality may be required.
239-
fn get_matched_indices_with_limit_offset<'a>(
239+
fn get_matched_indices_with_limit_offset(
240240
&self,
241-
iter: impl Iterator<Item = (usize, &'a u64)>,
241+
iter: &[u64],
242242
deleted_offset: Option<usize>,
243243
limit: usize,
244244
offset: JoinHashMapOffset,
@@ -251,30 +251,52 @@ pub trait JoinHashMapType {
251251
let mut match_indices = UInt64BufferBuilder::new(0);
252252

253253
let mut output_tuples = 0_usize;
254-
let mut next_offset = None;
255254

256255
let hash_map: &RawTable<(u64, u64)> = self.get_map();
257256
let next_chain = self.get_list();
258257

259-
let (initial_idx, initial_next_idx) = offset;
260-
'probe: for (row_idx, hash_value) in iter.skip(initial_idx) {
261-
let index = if initial_next_idx.is_some() && row_idx == initial_idx {
262-
// If `initial_next_idx` is zero, then input index has been processed
263-
// during previous iteration, and it can be skipped now
264-
if let Some(0) = initial_next_idx {
265-
continue;
258+
let to_skip = match offset {
259+
(initial_idx, None) => initial_idx,
260+
(initial_idx, Some(0)) => initial_idx + 1,
261+
(initial_idx, Some(initial_next_idx)) => {
262+
let mut i = initial_next_idx - 1;
263+
loop {
264+
let match_row_idx = if let Some(offset) = deleted_offset {
265+
// This arguments means that we prune the next index way before here.
266+
if i < offset as u64 {
267+
// End of the list due to pruning
268+
break;
269+
}
270+
i - offset as u64
271+
} else {
272+
i
273+
};
274+
match_indices.append(match_row_idx);
275+
input_indices.append(initial_idx as u32);
276+
output_tuples += 1;
277+
// Follow the chain to get the next index value
278+
let next = next_chain[match_row_idx as usize];
279+
280+
if output_tuples >= limit {
281+
let next_offset = Some((initial_idx, Some(next)));
282+
return (input_indices, match_indices, next_offset);
283+
}
284+
if next == 0 {
285+
// end of list
286+
break;
287+
}
288+
i = next - 1;
266289
}
267-
// Otherwise, use `initial_next_idx` as-is
268-
initial_next_idx
269-
} else if let Some((_, index)) =
290+
291+
initial_idx + 1
292+
}
293+
};
294+
295+
let mut row_idx = to_skip;
296+
for hash_value in &iter[to_skip..] {
297+
if let Some((_, index)) =
270298
hash_map.get(*hash_value, |(hash, _)| *hash_value == *hash)
271299
{
272-
Some(*index)
273-
} else {
274-
None
275-
};
276-
277-
if let Some(index) = index {
278300
let mut i = index - 1;
279301
loop {
280302
let match_row_idx = if let Some(offset) = deleted_offset {
@@ -294,8 +316,8 @@ pub trait JoinHashMapType {
294316
let next = next_chain[match_row_idx as usize];
295317

296318
if output_tuples >= limit {
297-
next_offset = Some((row_idx, Some(next)));
298-
break 'probe;
319+
let next_offset = Some((row_idx, Some(next)));
320+
return (input_indices, match_indices, next_offset);
299321
}
300322
if next == 0 {
301323
// end of list
@@ -304,9 +326,10 @@ pub trait JoinHashMapType {
304326
i = next - 1;
305327
}
306328
}
329+
row_idx += 1;
307330
}
308331

309-
(input_indices, match_indices, next_offset)
332+
(input_indices, match_indices, None)
310333
}
311334
}
312335

0 commit comments

Comments
 (0)