nat-openai commented on code in PR #6948:
URL: https://github.com/apache/datafusion-comet/pull/6948#discussion_r4316878470


##########
native/spark-expr/src/array_funcs/sequence.rs:
##########
@@ -184,39 +267,94 @@ where
             None => -1,
         }
     };
+    let scalar_length = if args
+        .iter()
+        .all(|arg| matches!(arg, ColumnarValue::Scalar(_)))
+    {
+        let s = start.value(0);
+        let e = stop.value(0);
+        Some(sequence_length(s, e, row_step(0, s, e))?)
+    } else {
+        None
+    };
+    let scalar_total = scalar_length
+        .map(|len| batch_length(len as u128 * num_rows as u128))
+        .transpose()?;
+    // Columns admit scratch first, then validate and cache each row length 
once before
+    // growing this same reservation for values. Scalars know their complete 
size already.
+    let initial = (|| {
+        let bitmap_count = if null_counts.iter().any(|count| *count != 0) {
+            2
+        } else {
+            0
+        };
+        let bytes = construction_scratch(num_rows, true, bitmap_count)?;
+        let bytes = bytes
+            .checked_add(value_bytes::<T>(scalar_total.unwrap_or(0))?)
+            .ok_or_else(size_error)?;
+        pool.map(|pool| reserve_construction(pool, bytes))
+            .transpose()
+    })();
+    // Keep error-only typed parsing in the cold helper so it need not borrow 
hot input locals.
+    let scratch_error = move |error| {
+        // Only resource exhaustion needs an allocation-free validation pass 
to keep Spark
+        // row errors and the batch element ceiling ahead of it. Propagate 
other pool errors,
+        // such as an interrupted Spark task, immediately without inspecting 
more input rows.
+        if scalar_length.is_none() && matches!(&error, 
DataFusionError::ResourcesExhausted(_)) {
+            validate_before_resource_error::<T>(args, num_rows, error)
+        } else {
+            error
+        }
+    };
+    let reservation = initial.map_err(scratch_error)?;
+    // Reuse validity once per batch instead of checking three inputs and 
rebuilding a
+    // bitmap row by row. Arrow unions sliced bitmaps with bulk word 
operations, allocating
+    // one accumulator and applying the remaining masks in place.
+    let input_nulls = [
+        start.nulls(num_rows),
+        stop.nulls(num_rows),
+        step.as_ref().and_then(|step| step.nulls(num_rows)),
+    ];
+    let nulls = NullBuffer::union_many(input_nulls.iter().map(Option::as_ref))
+        .filter(|nulls| nulls.null_count() != 0);
+    if nulls
+        .as_ref()
+        .is_some_and(|nulls| nulls.null_count() == num_rows)
+    {
+        // Reuse the accumulator so another all-null bitmap cannot exceed its 
capacity allowance.
+        return null_sequence::<T>(child_field, num_rows, nulls);
+    }
 
     // Validate every row before checking the batch total, preserving the 
first Spark error.
     // Retain lengths so short sequences do not repeat per-row arithmetic 
during generation.
-    let mut lengths = Vec::with_capacity(num_rows);
-    let mut total = 0usize;
-    for row in 0..num_rows {
-        if nulls.as_ref().is_some_and(|nulls| nulls.is_null(row)) {
-            lengths.push(0);
-            continue;
-        }
-        let s = start.value(row);
-        let e = stop.value(row);
-        let len = sequence_length(s, e, row_step(row, s, e))?;
-        total += len;
-        lengths.push(len);
-    }
-    // Arrow's List offsets must fit i32; Spark's per-row array limit is 
checked above.
-    if total > i32::MAX as usize {
-        return Err(DataFusionError::External(Box::new(
-            SparkError::SequenceBatchTooLarge {
-                total_elements: total.to_string(),
-            },
-        )));
+    let mut lengths = sequence_vec::<usize>(num_rows).map_err(scratch_error)?;
+    let total = if let Some(len) = scalar_length {
+        lengths.resize(num_rows, len);
+        scalar_total.unwrap()
+    } else {
+        let mut total = 0u128;
+        for row in 0..num_rows {
+            if nulls.as_ref().is_some_and(|nulls| nulls.is_null(row)) {
+                lengths.push(0);
+                continue;
+            }
+            let s = start.value(row);
+            let e = stop.value(row);
+            let len = sequence_length(s, e, row_step(row, s, e))?;
+            total += len as u128;
+            lengths.push(len);
+        }
+        batch_length(total)?
+    };
+    if scalar_length.is_none() {
+        if let Some(reservation) = &reservation {
+            reservation.try_grow(value_bytes::<T>(total)?)?;

Review Comment:
   > With 64 batches of 64 rows, 64-element BIGINT sequences, a 220,000-byte 
pool, and 64 KiB of sort spill reservation, the exact base kernel returns all 
4,096 rows with 47 spills.
   
   This does feel a little bit like a contrived example. On the main branch, 
the `sequence` memory reservations are untracked, and with such small 
sequences, there's probably ample system headroom for them to succeed. 
Combining that with a very small memory pool (for tracked memory) ensures that 
we can demonstrate spilling in the sort operator while hitting the limit when 
we try to reserve space in `sequence`.
   
   That's not to say I'm discounting this point in general though, and I'm 
considering those proposals.
   
   > Please coordinate admission with reclamation
   
   IIUC, this would mean adding pretty novel cross-native-operator memory 
coordination? If so, I image we need to think about the design of that pretty 
carefully.
   
   > or protect construction headroom before downstream buffering consumes it
   
   Codex points me toward [`reserve_memory_for_merge` in sort.rs as 
](https://github.com/apache/datafusion/blob/55.2.0/datafusion/physical-plan/src/sorts/sort.rs#L780-L795)
 as a precedent for this sort of thing. I'm trying to understand how this 
buffer could be sized such that it provides enough space for any computable 
input without starving other operators... guarantees seem hard to come by and 
it might remain possible to come up with adversarial counter examples (large 
sequences; small pools) that break any assumptions.



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