[ 
https://issues.apache.org/jira/browse/SPARK-59539?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

ASF GitHub Bot updated SPARK-59539:
-----------------------------------
    Labels: pull-request-available  (was: )

> Avoid per-row autoboxing in InSet code generation for integral and date/time 
> types
> ----------------------------------------------------------------------------------
>
>                 Key: SPARK-59539
>                 URL: https://issues.apache.org/jira/browse/SPARK-59539
>             Project: Spark
>          Issue Type: Improvement
>          Components: SQL
>    Affects Versions: 4.1.0
>            Reporter: David Mollitor
>            Priority: Minor
>              Labels: pull-request-available
>
> h2. Background
> InSet backs IN predicates with many elements (and x = a OR x = b OR ... 
> chains after OptimizeIn folds them). In whole-stage-generated code, 
> InSet.genCodeWithSet evaluates membership as set.contains(value) over a Scala 
> Set[Any]. Because contains takes Object, a primitive subject is autoboxed on 
> every row (a Long.valueOf/Integer.valueOf allocation per row), and the set 
> stores boxed elements.
> Only Byte/Short/Int/Date sets up to spark.sql.optimizer.inSetSwitchThreshold 
> (default 400) avoid this, via a JVM switch. The switch bytecode requires int 
> labels, so Long/Timestamp/TimestampNTZ can never use it and always take the 
> boxed path at every size; large Int/Date sets (> 400) also fall back to it.
> h2. Proposal
> For integral and date/time types whose Catalyst internal representation is a 
> primitive int or long (Long, Timestamp, TimestampNTZ, Int, Date, Short, 
> Byte), build a sorted primitive long[]/int[] on the driver and probe it in 
> generated code with java.util.Arrays.binarySearch — a primitive overload with 
> no autoboxing and no allocation. Byte/Short are widened to int.
> The change is gated by a new internal flag 
> spark.sql.optimizer.useSpecializedSetForInSet.enabled (default true) as an 
> operational fallback. It affects only whole-stage code generation: the 
> interpreted eval path (which receives an already-boxed value, so there is no 
> boxing to remove) and the switch path are unchanged, and the InSet node 
> itself is unchanged, so there is no plan-shape or golden-file change.
> Out of scope, kept on the existing boxed path:
>  - Float/Double — their SQL equality (NaN = NaN, -0.0 = +0.0) requires the 
> special handling the current path provides.
>  - String/Decimal/binary/complex — not primitive; UTF8String is already an 
> object, so there is no boxing to remove.
> h2. Benefit
> Membership over a Long column, 10M rows, non-matching values (worst case — 
> every row evaluates the full predicate). Current boxed InSet vs the 
> sorted-array binary search (local microbenchmark, indicative):
> ||set size||boxed InSet (ms)||sorted-array binarySearch (ms)||
> |20|107|40|
> |100|95|54|
> |500|151|82|
> ~1.7–2.7x faster with zero per-row allocation.
> h2. Correctness
> Semantics are unchanged. The generated three-valued-logic structure is 
> identical (match → true; non-match with a null in the list → null via the 
> existing hasNull handling; null subject handled by nullSafeCodeGen); only the 
> membership test changes from a boxed Set.contains to a primitive 
> Arrays.binarySearch. For the in-scope types, equality is exact primitive 
> comparison, so no special-value handling is required. Covered by existing and 
> new PredicateSuite tests.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

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

Reply via email to