Conversation
Keep Comet's kernel for custom seeds, structs, nested dictionaries, and Time64. Partial-closes apache#5103.
|
Thanks @sam-1112. Could you share benchmark results? |
|
Thanks @andygrove . I expanded the existing Criterion benchmark to cover the compatible type families and the fallback paths involved in this change. Benchmark coverageThe benchmark contains 17 cases covering batch size, null distribution, compatible type families, and fallback paths. The multi-column cases hash an
Additional 8,192-row cases cover:
The seed-42 compatible cases exercise the new Multi-column batch-size and null-distribution resultsLower execution time is better. Negative changes indicate improvement.
Type-family and fallback-path resultsThis table shows the latest paired run using a freshly captured baseline
RepeatabilityI repeated the type-family comparison because the local macOS runs showed cross-process variation. The following changes were reproducible:
Some cases changed direction between independent runs:
Because these are separate Criterion processes on a local macOS machine, I would treat the inconsistent cases as inconclusive rather than claim a reproducible improvement or regression. A dedicated Linux benchmark host would be appropriate if we need a firmer conclusion for those cases. Overall, the clearest reproducible wins are primitive, Decimal128, and dictionary inputs. The fallback paths do not show a consistent material change. |
sunchao
left a comment
There was a problem hiding this comment.
Correctness
Reviewed the complete six-file change at d2ebecd7 against fad62309. Previously, every xxhash64 call used Comet's kernel. This change delegates compatible inputs with Spark's default seed 42 to datafusion-spark, while retaining the original kernel for other seeds and unsupported shapes. No new or remaining verified P1/P2 findings.
Compared the implementation with the maintained Spark 3.5 and 4.0 hash expressions and XXH64. The adapter correctly removes Catalyst's trailing seed from upstream arguments, preserves signed integer widening and byte order, normalizes signed zero, skips nulls without advancing the seed, and chains columns in order. Empty byte strings remain distinct from empty collections. Structs, nested dictionaries and Time64 retain the fallback that preserves Comet's existing behavior.
The new native comparisons also cover wide decimals, but those do not establish Spark support: Spark's wide-decimal encoding differs, and the unchanged Scala serializer still rejects precision above 18 recursively. Unusual NaN payloads likewise retain an existing native/Spark difference. This PR does not introduce it.
Validation
The 49 added native test definitions cover both delegated and fallback paths. They were not executed locally. Scala additions use column-backed inputs, so constant folding does not remove the hash evaluation. Local source checks passed. An isolated comparison of the exact hashing algorithms produced 567 matching cases and reproduced 119 intentionally selected pre-existing NaN/wide-decimal differences across seven seeds. That probe did not execute the Arrow/DataFusion UDF, Spark or JNI.
At 2026-09-15 18:52 UTC, CI and CodeQL require workflow approval and have no jobs. Only labeling passed. Integration validation remains pending. Maintained Spark 3.4/4.1 source branches were unavailable, so this review makes no source-compatibility claim for those versions.
Performance
The expanded benchmark covers 17 cases across row counts, null densities, type families and fallback paths, with input construction outside the measured iteration. The author's measurements report repeatable gains for primitive, dictionary and decimal cases. The latest string/binary pair is 7.45% slower, with a reported 95% interval of +6.69% to +8.42%, but independent runs range from about 2% faster to 7.5% slower. Map and fallback measurements also vary. I treat these cases as inconclusive, not as demonstrated speedups or a stable regression. The report does not pin the executable/build or provide raw run artifacts, and I have not independently reproduced the timings.
The adapter adds per-batch type inspection, argument/field allocations and reference-count operations. Configuration initializes once. I found no newly introduced per-row adapter allocation. Existing optimized struct handling stays on Comet's fallback, and compatible nested-list paths already used recursive hashing.
Design
The guarded delegation is appropriate for incremental consolidation. Unconditional reuse would change nullable-struct and nested-dictionary behavior, while removing the original kernel would also affect arbitrary seeds and other callers. Keeping serialization, result typing and shared hash consumers unchanged confines the change to the intended expression path.
The tests explicitly demonstrate why incompatible shapes must stay on the fallback, which makes the boundary easier to maintain when upstream behavior improves.
Abstraction & complexity
The invocation helper, recursive compatibility predicate and cached configuration each have a concrete purpose. The predicate keeps the safety rules in one place. The test module stays outside production builds and covers both equivalence and known divergence. I found no unnecessary abstraction or actionable design simplification.
sunchao
left a comment
There was a problem hiding this comment.
Re-reviewed 8c1e2159 against 347d8cf3. This update merges main without changing the six-file xxhash64 contribution, its dependency versions or the serializer. The seed-42 routing and fallbacks remain intact. No new or remaining P1/P2 findings. My existing approval stands.
Current native CI passed all 49 added xxhash64 differential tests, within 1,500 passing tests and five skipped. The executed merge eda3070a has parents [347d8cf3, 8c1e2159] and a tree identical to this head. This supplies actual Arrow/DataFusion test execution. The previous isolated algorithm probe remains separate evidence. Spark 4.1 integration checks are still running, and no local Spark/JNI run was performed.
The benchmark implementation and report are unchanged. Their missing build provenance and variable string/binary, map and fallback results remain qualified as before. Neither new timings nor a new speedup claim were added. The existing NaN-payload difference and Spark fallback for decimal precision above 18 are unchanged.
At September 15, 20:34 UTC, CI shows 19 successful, 13 skipped and three running checks, with no failures. The merged CI policy runs Comet suites only on Spark 4.1 in the PR tier. Other runtime profiles wait for the merge queue or explicit labels.
sunchao
left a comment
There was a problem hiding this comment.
Rechecked 32bc2032 against 58ab5f61 and the previously reviewed 8c1e2159. The full authored diff and all six PR files are unchanged. The 140 incremental paths come from the base update. Seed/type guards, Spark serde, and shared HLL/shuffle hash paths retain the reviewed behavior. No new or remaining verified P1/P2 finding. The existing approval stands.
The 17-case native benchmark is unchanged, and the inherited fixture-helper change leaves its 0%, 10%, and 100% null inputs identical. The author's variable timing results remain historical. I did not rerun benchmarks or infer a new performance gain.
Validation was source comparison against maintained Spark 3.5/4.0, retained locked upstream source, and deterministic fixture checks. The previous native CI passed all 49 differential tests on the prior merge, not this revision. Current-head CI and CodeQL remain action_required with zero jobs. Only labeling passed. No local Spark/JNI suite was run, and maintained Spark 3.4/4.1 sources remain unavailable.
Which issue does this PR close?
Part of #5103 (xxhash64-consolidation only).
Rationale for this change
#5103 asked Comet to stop maintaining a parallel native
xxhash64implementation wheredatafusion-spark::SparkXxhash64already matches Spark. A full swap is not safe:SparkXxhash64hardcodes seed42, does not push a parent null mask into struct children (#5753), restarts nested-dictionary hashes from 42 when a list/map element is hashed as a one-row first column, and does not dispatchTime64.This PR therefore uses hybrid routing: compatible arguments at Spark's default seed go through
SparkXxhash64; everything else stays on Comet's kernel.murmur3is unchanged.create_xxhash64_hashesis kept forapprox_count_distinct(HLL++) and for the fallback path.What changes are included in this PR?
spark_xxhash64now delegates toSparkXxhash64when the trailing Catalyst seed is42and every argument type is compatible.FixedSizeList/LargeList), and maps.Struct(and any type containing one), so hidden child values of a NULL struct do not affect the hashDictionarynested in a list/mapTime64murmur3andcreate_xxhash64_hashesare left in place.xxhash64record the 2026-09-15 upstream routing decision.How are these changes tested?
native/spark-expr/src/hash_funcs/xxhash64_diff.rscompare Comet's kernel againstSparkXxhash64for primitives, both Decimal128 widths, dictionaries, lists, maps (Utf8/Int32, Int32/Utf8, Utf8/Utf8, Int32/Int32, Decimal), nested combinations, multi-column chaining, custom seeds, and the known incompatibilities (null structs, nested dictionaries, Time64).CometHashExpressionSuitenow selects bothhash()andxxhash64()on the existing type coverage, including custom seeds, structs, arrays, maps, dictionaries, and fuzz.Local checks: