viirya opened a new pull request, #5757:
URL: https://github.com/apache/datafusion-comet/pull/5757

   ## Which issue does this PR close?
   
   Closes #5756.
   
   ## Rationale for this change
   
   The dictionary fast path hashes each distinct dictionary value once and 
reuses that result
   for every key. It was selected by the column's position and restarted from a 
hardcoded seed:
   
   ```rust
   let first_col = i == 0;
   ...
   if !first_col { /* unpack and recurse */ } else {
       let mut dict_hashes = vec![42; dict_values.len()];
   ```
   
   Two things are wrong with that:
   
   - `create_hashes_internal!` also runs on **recursion**, so a dictionary 
nested in a list,
     struct or map arrives as the only column of its call and looks like a 
first column, even
     though the buffer already holds the hash accumulated for earlier elements 
of that row. That
     hash was discarded, so a dictionary-encoded list element hashed 
differently from the
     identical decoded value.
   - The **hardcoded 42** is wrong whenever the caller supplies its own seed, 
which
     `hash(col, seed)` and `xxhash64(col, seed)` allow, so even a genuine first 
column disagreed
     with its decoded form for a non-default seed.
   
   For a shuffle partitioning key that means equal keys can reach different 
partitions, breaking
   grouping and joins; the `hash()` and `xxhash64()` SQL functions are affected 
too.
   
   ## What changes are included in this PR?
   
   - The reuse is valid exactly when every row carries the same incoming hash, 
so that is what
     is checked now, rather than the column index.
   - The per-value hashes start from the seed the buffer actually holds instead 
of an assumed 42.
   - Both changes are in murmur3 and xxhash64, which share this structure.
   
   A top-level dictionary column keeps the optimisation.
   
   **On the cost of the check.** It is a scan of the hash buffer, and that is 
not free: running
   it for every column measured 17% slower on an int column and 11% on a string 
column in a
   local criterion benchmark. It is therefore done inside the dictionary arm, 
so only dictionary
   columns pay for it. After moving it, the same benchmark is back at the 
unmodified timings.
   
   ## How are these changes tested?
   
   `cargo test -p datafusion-comet-spark-expr` passes 713 + 5 tests.
   
   Three new tests, all failing without the change:
   
   - a dictionary as a list element, compared against the decoded array — 
hashes `3853467749`
     instead of `1401423033` before the fix
   - the same for xxhash64
   - a top-level dictionary column for both seed 42 and seed 7, which pins that 
the fast path
     survives and that a non-default seed is handled
   
   ## Additional context
   
   Found while reviewing nested hash partitioning keys (#5567), which makes 
this reachable from
   shuffle partitioning, but the defect predates it and reproduces on `main` 
unchanged.
   
   Worth recording how it was found, since it says something about the tests: 
comparing a batched
   hash against a per-row hash cannot catch this, because both sides run the 
same faulty branch.
   It took an independent leaf-by-leaf chaining oracle to surface it. Credit to 
Codex for that
   approach.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to