Skip to content

Commit 71a7ca5

Browse files
committed
make ready for review
1 parent 450777e commit 71a7ca5

1 file changed

Lines changed: 19 additions & 17 deletions

File tree

‎datafusion/common/src/hash_utils.rs‎

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -521,9 +521,8 @@ fn hash_generic_byte_view_array<T: ByteViewType>(
521521
#[cfg(not(feature = "force_hash_collisions"))]
522522
const DICT_SCATTER_LANES: usize = 8;
523523

524-
/// Fold `dict_hash` into `prev` when `MULTI_COL` (secondary column), else
525-
/// overwrite with `dict_hash` (first column). Const generic +
526-
/// `#[inline(always)]` makes it branch-free per instantiation.
524+
/// Fold `dict_hash` into `prev` when `MULTI_COL`, else
525+
/// overwrite with `dict_hash`.
527526
#[cfg(not(feature = "force_hash_collisions"))]
528527
#[inline(always)]
529528
fn maybe_combine_dict_hash<const MULTI_COL: bool>(prev: u64, dict_hash: u64) -> u64 {
@@ -540,9 +539,8 @@ fn maybe_combine_dict_hash<const MULTI_COL: bool>(prev: u64, dict_hash: u64) ->
540539
/// The 8-wide unroll is for performance, splitting the loop into
541540
/// `std::array::from_fn` (gather 8 dict hashes by key index) followed by a
542541
/// fixed-length inner loop encourages the compiler to overlap 8
543-
/// independent load latencies, saturating the gather pipeline on
544-
/// ARM64/x86 instead of serializing one lookup at a time. The scalar tail
545-
/// handles the non-multiple-of-8 remainder.
542+
/// independent load latencies, saturating the gather pipeline instead of
543+
/// serializing one lookup at a time.
546544
///
547545
/// Invariants: `keys.len() == hashes.len()`; no key or dict value referenced
548546
/// here is null.
@@ -571,6 +569,7 @@ fn scatter_dict_hashes_no_null_checks<
571569
maybe_combine_dict_hash::<MULTI_COL>(hash_group[lane], lane_hashes[lane]);
572570
}
573571
}
572+
// need to handle the remainder for non 64 byte multiples
574573
for (key, hash) in keys_tail.iter().zip(hashes_tail.iter_mut()) {
575574
let dict_hash = unsafe { *dict_hashes.get_unchecked(key.as_usize()) };
576575
*hash = maybe_combine_dict_hash::<MULTI_COL>(*hash, dict_hash);
@@ -582,7 +581,7 @@ fn scatter_dict_hashes_no_null_checks<
582581
/// nullable-values case; the per-lane `is_valid` check is unavoidable.
583582
#[cfg(not(feature = "force_hash_collisions"))]
584583
#[inline(always)]
585-
fn scatter_dict_hashes_skip_null_values<
584+
fn scatter_dict_hashes_skip_value_nulls<
586585
K: ArrowDictionaryKeyType,
587586
const MULTI_COL: bool,
588587
>(
@@ -600,10 +599,7 @@ fn scatter_dict_hashes_skip_null_values<
600599
}
601600
}
602601

603-
/// Scatter dict-value hashes inside a mixed-validity 64-key chunk, with
604-
/// per-lane key-validity given by `mask`. All-valid-values path: branchless
605-
/// 64-lane gather + csel-blend by `mask`. Possibly-null-values path:
606-
/// bit-walk set lanes via `trailing_zeros`.
602+
/// Scatter inside a mixed-validity 64-key chunk governed by `mask`.
607603
#[cfg(not(feature = "force_hash_collisions"))]
608604
#[inline(always)]
609605
fn scatter_dict_hashes_in_masked_chunk<
@@ -633,8 +629,10 @@ fn scatter_dict_hashes_in_masked_chunk<
633629
}
634630
return;
635631
}
632+
// null-key lanes carry garbage indices; we gather them anyway and mask the result out below
636633
let max_valid_idx = dict_hashes.len().saturating_sub(1);
637634
for lane_base in (0..64).step_by(DICT_SCATTER_LANES) {
635+
// gather 8 dict hashes in parallel; clamp keeps garbage indices in-bounds
638636
let lane_hashes: [u64; DICT_SCATTER_LANES] = std::array::from_fn(|lane_offset| {
639637
let raw_idx =
640638
unsafe { keys.get_unchecked(lane_base + lane_offset).as_usize() };
@@ -653,10 +651,10 @@ fn scatter_dict_hashes_in_masked_chunk<
653651

654652
/// Scatter precomputed dictionary value hashes to key positions.
655653
///
656-
/// Const generics eliminate runtime branching:
657-
/// - `HAS_NULL_KEYS`: dictionary has null keys to skip
658-
/// - `HAS_NULL_VALUES`: dictionary values may be null (per-index check)
659-
/// - `MULTI_COL`: combine with existing hash vs. overwrite
654+
/// Uses const generics to eliminate runtime branching in the hot loop:
655+
/// - `HAS_NULL_KEYS`: Whether to check for null dictionary keys
656+
/// - `HAS_NULL_VALUES`: Whether to check for null dictionary values
657+
/// - `MULTI_COL`: Whether to combine with existing hash (true) or initialize (false)
660658
#[cfg(not(feature = "force_hash_collisions"))]
661659
#[inline(never)]
662660
fn hash_dictionary_scatter<
@@ -674,7 +672,7 @@ fn hash_dictionary_scatter<
674672

675673
if !HAS_NULL_KEYS {
676674
if HAS_NULL_VALUES {
677-
scatter_dict_hashes_skip_null_values::<K, MULTI_COL>(
675+
scatter_dict_hashes_skip_value_nulls::<K, MULTI_COL>(
678676
keys,
679677
dict_values.as_ref(),
680678
dict_hashes,
@@ -694,13 +692,15 @@ fn hash_dictionary_scatter<
694692
let bit_chunks = null_buffer.inner().bit_chunks();
695693
let chunk_len = bit_chunks.chunk_len();
696694

695+
// Walk the key null buffer 64 bits at a time; bit i set = lane i's key is non-null.
697696
for (chunk_idx, mask) in bit_chunks.iter().enumerate() {
698697
let base = chunk_idx * 64;
699698
let keys_chunk = &keys[base..base + 64];
700699
let hashes_chunk = &mut hashes_buffer[base..base + 64];
701700
if mask == u64::MAX {
701+
// all 64 keys valid: dense helper
702702
if HAS_NULL_VALUES {
703-
scatter_dict_hashes_skip_null_values::<K, MULTI_COL>(
703+
scatter_dict_hashes_skip_value_nulls::<K, MULTI_COL>(
704704
keys_chunk,
705705
dict_values.as_ref(),
706706
dict_hashes,
@@ -714,6 +714,7 @@ fn hash_dictionary_scatter<
714714
);
715715
}
716716
} else if mask != 0 {
717+
// mixed: masked-chunk helper (mask == 0 falls through, nothing to write)
717718
scatter_dict_hashes_in_masked_chunk::<K, HAS_NULL_VALUES, MULTI_COL>(
718719
keys_chunk,
719720
dict_values.as_ref(),
@@ -724,6 +725,7 @@ fn hash_dictionary_scatter<
724725
}
725726
}
726727

728+
// non-64 multiple: scalar path for the tail
727729
let remainder_len = bit_chunks.remainder_len();
728730
if remainder_len > 0 {
729731
let remainder = bit_chunks.remainder_bits();

0 commit comments

Comments
 (0)