peter-toth opened a new pull request, #58523:
URL: https://github.com/apache/spark/pull/58523

   ### What changes were proposed in this pull request?
   
   `InternalRowComparableWrapper` gains a public `orderingFor(dataTypes)` that 
returns the ordering from its existing `orderingCache`, and the two 
storage-partitioned join sites that built the same ordering by hand now go 
through it.
   
   `KeyedPartitioning.groupedKeyRowOrdering` called 
`RowOrdering.createNaturalAscendingOrdering` directly, which is byte-for-byte 
the `loadFunc` of that cache. `DataSourceV2ScanExecBase.outputPartitioning` did 
the same to sort the partition keys it then hands to `KeyedPartitioning`.
   
   ### Why are the changes needed?
   
   Two reasons, and the second is worth more than it looks.
   
   **One definition instead of two.** `InternalRowComparableWrapper.equals` is 
`ordering.compare(row, other.row) == 0` over the cached ordering, while 
`groupedKeyRowOrdering` is what lays grouped partition keys out. So "two 
partition keys are equal" and "two partition keys sort together" already had to 
be the same relation, and they were, only by both call sites happening to name 
the same function.
   
   **The direct call is expensive.** Only the Janino step inside 
`GenerateOrdering` is cached, keyed on the generated source. Everything 
upstream re-runs per call: `ExpressionCanonicalizer` over each `SortOrder`, 
building the whole Java source, `CodeFormatter.stripOverlappingComments` 
rebuilding it line by line, then hashing the multi-KB body to probe the compile 
cache. Measured in this worktree, for a two-column key:
   
       RowOrdering.createNaturalAscendingOrdering    93-105 us per call
       InternalRowComparableWrapper.orderingFor      0.16-0.6 us per call
   
   `DataSourceV2ScanExecBase.outputPartitioning` is the site where that 
repeats. It is a `def`, and an instrumented run of 
`KeyGroupedPartitioningSuite` counted 31,922 calls across 1,167 scan instances, 
a mean of 27 per scan. The other callers are once-per-something and get the 
same ~100 us back as noise: `GroupPartitionsExec.groupAndSortByKeys` runs from 
a `lazy val`, `EnsureRequirements` runs once per join per pass, and 
`KeyedPartitioning.keyRowOrdering` is itself a `lazy val`.
   
   Nothing gets slower. A `NonFateSharingCache` hit measured 0.163 us 
single-threaded and 0.334 us with 16 threads on one key. The cached value is a 
stateless comparator, so eviction costs a rebuild and nothing else, and almost 
every type list the new callers look up is one the wrapper path already loads 
for the same rows. The exception is `KeyedPartitioning.keyRowOrdering`, which 
keys on `keyDataTypes`, and that falls back to the expression types when there 
are no partition keys, so it can add an entry of its own.
   
   `SortMergeJoinEvaluatorFactory` also calls `createNaturalAscendingOrdering` 
directly and is deliberately left alone. Those are join-key rows from an 
`UnsafeProjection`, never wrapped, and their counterpart is the child `Sort`'s 
ordering rather than wrapper equality. They also run per partition on 
executors, so feeding their schemas into this cache would evict partition-key 
entries for no gain.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No. The cache's load function is the call that was being made directly, so 
it produces the same ordering.
   
   One caveat, pre-existing on the wrapper path and now extended to these two 
sites. `createNaturalAscendingOrdering` goes through 
`CodeGeneratorWithInterpretedFallback`, which reads 
`spark.sql.codegen.factoryMode` on every call, while the cache reads it once 
per type list for the JVM's lifetime. So a test that flips that internal conf 
after an entry is loaded now gets the ordering built under the earlier mode. 
The two orderings agree semantically, so this costs coverage rather than 
correctness.
   
   ### How was this patch tested?
   
   A new `InternalRowComparableWrapperSuite`, which the class did not have, 
only a benchmark. It asserts that `groupedKeyRowOrdering` and a wrapper hold 
one ordering instance, and fails if the delegation is reverted.
   
   Identity is what the test asserts because identity is what changes. Both 
sides were already built by the same function, so no comparison of rows can 
tell the two apart. What one instance buys is that neither side can later be 
given a definition the other does not have.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Opus 5)
   


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