david-mollitor-db opened a new pull request, #58284:
URL: https://github.com/apache/spark/pull/58284

   ### What changes were proposed in this pull request?
   
   Migrate the production `BinaryType` comparison call sites off the hand-rolled
   word-at-a-time `ByteArray.compareBinary(byte[], byte[])` onto the
   `java.util.Arrays.compareUnsigned(byte[], byte[])` intrinsic, which the JIT 
compiles to
   vectorized (SIMD) code on JDK 9+:
   
   - **`CodegenContext.genComp`** (`case BinaryType`) — the generated 
row/ordering comparators
     used by `GenerateOrdering` (ORDER BY, sort-merge join keys, array/map 
element comparison).
   - **`PhysicalBinaryType.ordering`** — the interpreted 
`Ordering[Array[Byte]]`.
   
   This mirrors the sibling `genComp`/`genEqual` path, which already emits
   `java.util.Arrays.equals` for `BinaryType`, so it extends an existing 
pattern.
   
   The now-unused 2-arg `ByteArray.compareBinary(byte[], byte[])` overload is 
retained as a
   benchmark baseline, with a JavaDoc pointing callers to 
`Arrays.compareUnsigned`. The off-heap
   overload (base object + offset) is unchanged and still used by `UTF8String` 
/ `BinaryView`.
   
   `ByteArrayBenchmark` gains an old-vs-intrinsic A/B case per size bucket.
   
   ### Why are the changes needed?
   
   `Arrays.compareUnsigned` is a JDK intrinsic that vectorizes the comparison, 
and it is faster than
   the hand-rolled `Unsafe` word-at-a-time loop for the realistic `BinaryType` 
value range
   (hashes, UUIDs, digests, blobs, i.e. `>= 8` bytes). It is also simpler and 
less error-prone than
   maintaining a bespoke comparison, and it matches the intrinsic already used 
on the equality path.
   
   **Semantics are unchanged.** The previous implementation compared bytes as 
unsigned (`& 0xFF`) and
   returned the length difference on a common prefix — exactly 
`Arrays.compareUnsigned`'s contract.
   The returned magnitude on a mismatch may differ, but the sign is identical, 
and all callers are
   `Ordering`s that only use the sign.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No.
   
   ### How was this patch tested?
   
   Existing tests, unmodified, validate the unchanged semantics:
   
   - `OrderingSuite` (catalyst) — including "SPARK-21344: BinaryType comparison 
...", which asserts
     `compare(Array(1), Array(-1)) < 0` (unsigned). Exercises the generated 
comparator with the new
     intrinsic.
   - `ByteArraySuite` (common/unsafe) — `testCompareBinary` unsigned assertions.
   
   Performance was measured with the updated `ByteArrayBenchmark` (old 
hand-rolled
   `ByteArray.compareBinary` vs the `java.util.Arrays.compareUnsigned` 
intrinsic). Best time in ms,
   lower is better; speedup = old / intrinsic. Measured locally on an AWS box, 
so treat the absolute
   numbers as indicative and the ratios as the takeaway.
   
   OpenJDK 17.0.20:
   
   | Input size    | `compareBinary` (ms) | `Arrays.compareUnsigned` (ms) | 
Speedup |
   
|---------------|----------------------|-------------------------------|---------|
   | 2-7 byte      | 188                  | 228                           | 
0.82x   |
   | 8-16 byte     | 449                  | 209                           | 
2.15x   |
   | 16-32 byte    | 466                  | 216                           | 
2.16x   |
   | 512-1024 byte | 611                  | 433                           | 
1.41x   |
   | 512 byte slow | 1599                 | 1183                          | 
1.35x   |
   
   OpenJDK 21.0.12:
   
   | Input size    | `compareBinary` (ms) | `Arrays.compareUnsigned` (ms) | 
Speedup |
   
|---------------|----------------------|-------------------------------|---------|
   | 2-7 byte      | 188                  | 216                           | 
0.87x   |
   | 8-16 byte     | 371                  | 199                           | 
1.86x   |
   | 16-32 byte    | 368                  | 198                           | 
1.86x   |
   | 512-1024 byte | 485                  | 442                           | 
1.10x   |
   | 512 byte slow | 1684                 | 1288                          | 
1.31x   |
   
   The intrinsic wins for all input sizes `>= 8` bytes, with the largest gains 
in the 8-32 byte
   range. "512 byte slow" is the worst case where both arrays differ only in 
the last byte, forcing a
   full-length scan. The only regression is sub-8-byte arrays (~13-18% slower): 
for anything under
   8 bytes, `Arrays.compareUnsigned` falls back to a byte-at-a-time loop (no 
full 8-byte word to
   vectorize over), which is exactly the regime the hand-rolled word-at-a-time 
comparison was
   designed for. Since realistic `BinaryType` values are `>= 8` bytes, the 
vectorized path dominates
   in practice.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Opus 4.8)
   


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