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

   ## Which issue does this PR close?
   
   Closes #5777.
   
   ## Rationale for this change
   
   `hash_list_array!` is the fallback for a list whose elements are not 
primitives, which is the
   path `array<struct<..>>` takes. It sliced a one-element Arrow array per 
element and re-entered
   the hash dispatch for it, so each element cost an allocation plus a full 
type match, while a
   list of primitives goes through a vectorized path and avoids both.
   
   That is the shape behind 
`spark.comet.shuffle.native.partitioning.hash.nested.enabled`
   defaulting to off in #5567.
   
   ## What changes are included in this PR?
   
   Spark chains the element hashes in order, so the elements of one row still 
have to be hashed in
   sequence, but the allocation and dispatch do not have to happen per element. 
This hashes one
   element per row at a time in a single batched call, seeding each slot with 
the running hash of
   its row — exactly what the per-element call did. Rows are independent, so 
the dispatch count
   drops from the total element count to the length of the longest list.
   
   Two details make it a win rather than a trade:
   
   - **Surviving rows are carried forward** instead of the batch being 
rescanned each pass. A row
     never becomes alive again once exhausted. Rescanning costs rows x 
longest-list, which for one
     long list among short ones is almost all wasted — 8192 rows where one 
holds 1024 elements and
     the rest hold one would scan 8.4M slots to hash 9215 elements.
   - **A simpler walk when every row is non-null with the same length**, 
recorded in the same
     initialisation pass. No row drops out early there, so the survivor 
bookkeeping would be pure
     overhead; without this the uniform shapes regressed 6-8%.
   
   The gather is indexed by the list's own offset width, since a `LargeList` 
can hold more than
   `u32::MAX` elements and narrowing the positions would silently wrap and hash 
the wrong elements.
   The implementation this replaces used `usize` throughout, so this keeps that 
property.
   
   ## How are these changes tested?
   
   Measured with the `hash` benchmark already on main from #5765, so the 
numbers can be reproduced
   against `main` rather than against a benchmark arriving with the change:
   
   | case | main | this PR |
   |---|---|---|
   | `array<int32>` x10 | 146 µs | 147 µs |
   | `array<struct<..>>` x10 | 10162 µs | **729 µs** |
   | `array<struct<..>>` skewed | 1192 µs | **568 µs** |
   
   8192 rows, Apple M4 Max. The primitive-element case is untouched, as 
expected — it does not go
   through this path.
   
   The hash values must not move, so the tests are mostly about that. Golden 
values for
   `array<struct<..>>` pin empty lists, null lists, null struct elements, null 
fields and repeated
   values; a separate test asserts element order still changes the hash. Then 
the cases this
   implementation introduces: skewed lengths and descending lengths (so the 
uniform-length path is
   not taken), `LargeList` against `List` on the same data, `array<array<int>>` 
where the element is
   itself a list, rows exhausting on different passes, a sliced list whose 
first offset is non-zero,
   and a null list that still covers a non-empty element range. An off-by-one 
in the survivor
   condition fails several of them.
   
   `cargo test -p datafusion-comet-spark-expr` passes 729 + 7, 
`CometHashExpressionSuite` 40 and
   `CometNativeShuffleSuite` 53. `cargo fmt --check` and `clippy -D warnings` 
are clean.
   
   ## Additional context
   
   This does not flip the config default. `array<struct<..>>` as a partitioning 
key is now faster
   than letting Spark do the shuffle rather than slower, but I would rather 
change the default in a
   separate PR with shuffle-level numbers than fold it in here.
   
   Further headroom exists and is not taken here: profiling the remaining time 
puts about 38% in
   Arrow `take`, most of that copying string payloads. Removing it means 
hashing fields directly at
   gathered indices, which requires making the shared hash dispatch index-aware 
— a much wider
   change to a macro whose correctness decides partition assignment.
   


-- 
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