andygrove opened a new issue, #5349:
URL: https://github.com/apache/datafusion-comet/issues/5349

   ## Summary
   
   `sequence` is the highest-upside remaining codegen-dispatched expression. 
The dispatcher kernel makes
   **3 heap allocations per non-null row and passes over every generated 
element three times**, and two
   of those allocations scale with the sequence length rather than with the row 
count. For a 365-element
   date spine that is roughly **48 MB of transient `long[]` garbage per 
8192-row batch**.
   
   Assessed with the `suggest-native-expression` skill: **compatibility 
confidence Medium** (clean
   plan-time guard, see scope), **native upside High**, and the upside is 
**measured from the emitted
   kernel**, not inferred from Spark's source.
   
   Recommendation is scoped: implement the integral element type natively, keep 
the date and timestamp
   variants on the dispatcher.
   
   ## How it runs today
   
   `object CometSequence extends CometCodegenDispatch[Sequence]`
   (`spark/src/main/scala/org/apache/comet/serde/arrays.scala:867`). A plain 
`CometCodegenDispatch` means
   the dispatcher is the only path and `getSupportLevel` is `Compatible()`.
   
   `ArrayType(IntegralType)` is inside 
`CometBatchKernelCodegen.isSupportedDataType`, and I confirmed the
   kernel really does compile (`CometBatchKernelCodegen.compile` with two 
nullable `BigIntVector` inputs
   returns without throwing), so there is **no** hidden whole-operator Spark 
fallback here. The only
   fallback trigger is `spark.comet.exec.scalaUDF.codegen.enabled=false`.
   
   ## Native upside: High (measured)
   
   Dumped the real kernel via `CometBatchKernelCodegen.generateSource` for
   `Sequence(BoundReference(0, LongType), BoundReference(1, LongType), None, 
Some("UTC"))` over two
   nullable `BigIntVector` inputs. The relevant emitted Java, verbatim:
   
   ```java
   arr_0 = new long[i_1];
   while (i_1 > 0) {
     i_1--;
     arr_0[i_1] = (long) (value_1 + (value_1 <= value_2 ? 1L : -1L) * i_1);
   }
   value_0 = UnsafeArrayData.fromPrimitiveArray(arr_0);
   ```
   
   and on the output side:
   
   ```java
   org.apache.spark.sql.catalyst.util.ArrayData arr_1 = value_5;
   int n_0 = arr_1.numElements();
   int cidx_0 = output.startNewValue(i);
   for (int j_0 = 0; j_0 < n_0; j_0++) {
     outListChild_0.setSafe(cidx_0 + j_0, arr_1.getLong(j_0));
   }
   output.endValue(i, n_0);
   ```
   
   Per non-null row, where `n` is the sequence length:
   
   | # | Allocation | Size |
   | --- | --- | --- |
   | 1 | `new long[n]` (the element array) | O(n) |
   | 2 | `new long[totalSizeInLongs]` inside 
`UnsafeArrayData.fromPrimitiveArray` (`UnsafeArrayData.java:431`) | O(n) |
   | 3 | `new UnsafeArrayData()` (`UnsafeArrayData.java:439`) | constant |
   
   Plus **three passes over every element**: the fill loop, the 
`Platform.copyMemory` inside
   `fromPrimitiveArray`, and the output-side `getLong` / `setSafe` loop.
   
   Concretely, for a 365-element date spine at the default batch size: 2 arrays 
× 365 × 8 bytes × 8192
   rows = **~48 MB of transient `long[]` per batch**, all on the JVM heap and 
therefore invisible to
   Comet's native memory pool, plus ~3M element writes where one pass would do.
   
   A native kernel writes values straight into the Arrow `ListArray` child 
buffer and pushes offsets:
   one pass, zero per-row heap allocation, no intermediate `ArrayData` 
representation at all.
   
   Honest limits:
   
   - Workload presence is **unmeasured**. `grep` over `benchmarks/tpc/queries/` 
finds no `sequence`
     usage. `explode(sequence(start, stop, interval 1 day))` for date spines is 
a common ETL shape, but
     that is a judgment call rather than evidence.
   - The 48 MB figure is arithmetic on the measured allocation sites, not a 
heap profile.
   
   ## Compatibility assessment: Medium (with a clean plan-time guard)
   
   ### Spark versions
   
   Behavior is **stable** across 3.4.3, 3.5.8, 4.0.1, and 4.1.1. Diffing the 
`Sequence` class body
   across the four tags shows only:
   
   - 3.4.3 → 3.5.8: internal refactors (`startType.sameType` → 
`DataTypeUtils.sameType`,
     `iType.integral` → `PhysicalIntegralType.integral`).
   - 3.5.8 → 4.0.1: adds `override lazy val throwable: Boolean = 
stepOpt.isDefined` (an optimizer hint)
     and `code.toString` in `doGenCode`.
   - 4.0.1 → 4.1.1: byte-identical.
   
   No shims needed. `TimeType` is not accepted by `sequence` on 4.1.
   
   ### Hazard checklist
   
   | Hazard | Applies? |
   | --- | --- |
   | JVM formatting APIs | No |
   | `java.util.regex` features | No |
   | Collation on Spark 4.0+ | No (no string inputs) |
   | `BigDecimal` / `MathContext` | No |
   | Timezone and calendar | **Yes, for the temporal element types only.** 
`TemporalSequenceImpl` / `PeriodSequenceImpl` / `DurationSequenceImpl` carry a 
`zoneId` and step through `DateTimeUtils.timestampAddInterval`, so DST 
transitions, month-length arithmetic, and the `spark.sql.legacy.*` calendar 
flags are all in play. |
   | ANSI error class and message parity | **Yes, bounded.** Two conditions, 
both enumerable: `_LEGACY_ERROR_TEMP_3243` ("Illegal sequence boundaries") when 
the step direction does not match the bounds, and `Sequence.sequenceLength`'s 
overflow / `MAX_ROUNDED_ARRAY_LENGTH` path. `step == 0 && start == stop` is 
legal and yields a single element. |
   | Float/double text conversion | No |
   | Invalid UTF-8 | No |
   | Lambda bodies / arbitrary Catalyst trees | No |
   | JVM session state beyond a scalar config | No (the timezone arrives as 
`timeZoneId` on the node) |
   | Nondeterminism / ordering | No |
   | Cross-version behavioral differences | No (see above) |
   
   The timezone hazard is what makes this Medium rather than High. It is fully 
contained in the
   temporal element types, and `Sequence.impl` selects on 
`dataType.elementType`, so the split is
   **knowable at plan time from `start.dataType`** with no runtime probing.
   
   ### Upstream is not usable
   
   - `datafusion-spark` 54.1.0 has no `sequence`.
   - `datafusion-functions-nested` has `range` / `generate_series`, but the 
semantics differ (for
     example `"Cannot generate date range less than 1 day."` at `range.rs:379` 
and a different
     zero-step message at `range.rs:460`), and they are shaped for scalar 
arguments rather than a
     per-row columnar sequence.
   
   So this needs a Comet kernel.
   
   ## Proposed approach
   
   See the `implement-comet-expression` skill.
   
   1. Add a native `spark_sequence` under `native/spark-expr/`, handling 
`Int8`/`Int16`/`Int32`/`Int64`
      start, stop, and step. Compute each row's length with Spark's overflow 
rules, reserve the child
      buffer once for the whole batch, write values directly, push offsets. No 
per-row allocation.
   2. Reproduce the two error conditions with matching classes and messages
      (`_LEGACY_ERROR_TEMP_3243` and the array-size-limit error).
   3. Change `CometSequence` from `CometCodegenDispatch[Sequence]` to a 
`CometExpressionSerde[Sequence]`
      with `CodegenDispatchFallback`, returning `Unsupported(Some(...))` from 
`getSupportLevel` when
      `start.dataType` is not an `IntegralType` so date and timestamp sequences 
keep running on the
      dispatcher exactly as they do today.
   4. Add `native/spark-expr/benches/sequence.rs` covering the shapes from
      `optimizing_expressions.md`: short sequences (2 to 5 elements), long 
sequences (365, 10k),
      ascending / descending / zero-step, sparse and dense nulls, and the error 
path.
   5. Add Comet SQL Tests at 
`spark/src/test/resources/sql-tests/expressions/array_funcs/sequence.sql`.
   
   ### Non-goals
   
   - Native `DateType` / `TimestampType` / `TimestampNTZType` sequences. These 
stay on the dispatcher
     until someone takes on the timezone, DST, and legacy-calendar work as a 
separate issue.
   - Any change to `explode`, which is where `sequence` output usually goes.
   
   ## Acceptance criteria
   
   - Output bit-identical to the codegen-dispatch path for all integral element 
types, including null
     placement, `containsNull = false` on the result array, `step == 0 && start 
== stop`, and both error
     conditions with matching error class and message.
   - Date and timestamp sequences demonstrably still route through the 
dispatcher (assert on the
     `[COMET-INFO: JVM codegen dispatcher: ...]` EXPLAIN segment).
   - Criterion benchmark committed, no regression on any shape.
   - The `sequence` entry in 
`docs/source/contributor-guide/expression-audits/array_funcs.md` updated
     when the work lands.
   
   ---
   
   Filed by the `suggest-native-expression` skill. Motivation:
   [Native Coverage for Codegen-Dispatched 
Expressions](https://github.com/apache/datafusion-comet/blob/main/docs/source/contributor-guide/roadmap.md#native-coverage-for-codegen-dispatched-expressions).
   The assessment is recorded in
   `docs/source/contributor-guide/expression-audits/array_funcs.md` under `## 
sequence`.
   Previous run of the same skill produced #5347.
   


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