andygrove commented on code in PR #6861:
URL: https://github.com/apache/datafusion-comet/pull/6861#discussion_r4241898969


##########
native/shuffle/src/spark_unsafe/row.rs:
##########
@@ -1423,8 +1440,17 @@ pub fn process_sorted_row_partition(
         frozen.clear();
         let mut cursor = Cursor::new(&mut frozen);
 
-        let block_writer = 
ShuffleBlockWriter::try_new(batch.schema().as_ref(), codec.clone())?;
-        written += block_writer.write_batch(&batch, &mut cursor, &mut 
codec_context, &ipc_time)?;
+        // The schema changes between batches when a dictionary-encoded column 
falls back to a
+        // plain array (see `builder_to_array`).
+        if block_writer

Review Comment:
   Nit, optional: the cache invalidation here has no test. If I replace the 
condition with `block_writer.is_none()`, all 172 unit tests in the shuffle 
crate still pass. Without the schema check, a column that goes from plain to 
dictionary fails the write with `Ipc error: no dict id for field`. One that 
goes from dictionary to plain writes a block that decodes to the wrong type. I 
wrote a throwaway test that calls `process_sorted_row_partition` with 
`batch_size = 4` and a dictionary ratio of 1.5 over `x,x,x,x,a,b,c,d,y,y,y,y`. 
The column goes dictionary, plain, dictionary inside one call, and the PR 
decodes all three blocks correctly. Could we add a test like that? It needs 
only a few one-column `UnsafeRow`s, laid out the way `benches/row_columnar.rs` 
does.



##########
native/spark-expr/src/string_funcs/split.rs:
##########
@@ -445,18 +445,24 @@ fn push_split_parts<O: OffsetSizeTrait>(
     builder: &mut GenericStringBuilder<O>,
 ) {
     if limit == 0 {

Review Comment:
   Nit, optional: nothing in the repo would notice if this branch were wrong. 
`test_split_limit_zero` goes through `split_string`, which calls 
`split_with_regex`, and this PR doesn't touch that helper. The only other 
`limit = 0` test I found splits `a,b,c,d,e`, which has no empty parts. I broke 
the branch three ways (kept the trailing empties, dropped the interior ones, 
removed the `[""]` fallback) and all 16 split tests still passed. The logic 
itself looks right to me. I compared it with the old branch on every string up 
to length 8 over a three-character alphabet with a handful of patterns, about 
88k cases, and they matched. Could we add a test through `spark_split` with 
limit 0 on `a,b,c,,`, `a,,b`, `,a`, `a,,b,,` and the empty string? A 
`split_rust.sql` query with the same inputs would cover it end to end. I'd 
leave `,,` out of the SQL file since Java returns an empty array for it and the 
native path returns `[""]`.



##########
native/shuffle/src/spark_unsafe/row.rs:
##########
@@ -517,9 +521,12 @@ fn append_nested_struct_fields_field_major(
                 let nested_builder = get_field_builder!(struct_builder, 
StructBuilder, field_idx);
 
                 // Collect nested struct addresses and sizes in one pass, 
building validity
-                let mut nested_addresses: Vec<jlong> = 
Vec::with_capacity(num_rows);
-                let mut nested_sizes: Vec<jint> = Vec::with_capacity(num_rows);
-                let mut nested_is_null: Vec<bool> = 
Vec::with_capacity(num_rows);
+                nested_addresses.clear();

Review Comment:
   Nit, optional: same question for the scratch vectors here and at line 981. 
Reusing them only matters when a struct has two or more nested struct fields, 
and I didn't see a case with that shape in `CometColumnarShuffleSuite`. If I 
remove the three `clear()` calls in both functions, the shuffle crate's tests 
still pass. A struct with `a: struct<x>`, `b: struct<y>` and `m: struct<c: 
struct<p>, d: struct<q>>`, with a null at each level, passes on the PR and 
fails without the `clear()` calls. Could the test above also cover that shape?



##########
native/spark-expr/src/bloom_filter/spark_bloom_filter.rs:
##########
@@ -157,19 +156,19 @@ impl SparkBloomFilter {
     /// Serializes a SparkBloomFilter to a byte array conforming to Spark's 
BloomFilter
     /// binary format. The output format follows the filter's `version`.
     pub fn spark_serialization(&self) -> Vec<u8> {
-        let mut out: Vec<u8> = (self.version.to_int() as 
u32).to_be_bytes().to_vec();
-        out.append(&mut self.num_hash_functions.to_be_bytes().to_vec());
+        // At most a 16-byte header (version, numHashFunctions, seed, 
numWords), then the words.
+        let mut out: Vec<u8> = Vec::with_capacity(16 + self.bits.word_size() * 
8);
+        out.extend_from_slice(&(self.version.to_int() as u32).to_be_bytes());
+        out.extend_from_slice(&self.num_hash_functions.to_be_bytes());
         if let SparkBloomFilterVersion::V2 = self.version {
             // Spark's BloomFilterImplV2.writeTo writes the seed between
             // numHashFunctions and the bit array.
-            out.append(&mut (self.seed as u32).to_be_bytes().to_vec());
+            out.extend_from_slice(&(self.seed as u32).to_be_bytes());
         }
-        out.append(&mut (self.bits.word_size() as u32).to_be_bytes().to_vec());
-        let mut filter_state: Vec<u64> = self.bits.data();
-        for i in filter_state.iter_mut() {
-            *i = i.to_be();
+        out.extend_from_slice(&(self.bits.word_size() as u32).to_be_bytes());
+        for word in self.bits.data() {

Review Comment:
   Nit, optional: in a local A/B against the old code, this per-word 
`extend_from_slice` loop ran at about the same speed. It was a few microseconds 
either way from 64 KiB to 1 MiB, and the sign flipped between runs at 8 MiB. 
Peak memory is 3x lower, which is the real win. Filling the presized buffer 
with `resize` and `chunks_exact_mut(8)` ran about 2.5x faster than this loop at 
1 MiB, roughly 18 µs against 50 µs, with one allocation and byte-identical 
output. Could we use that form?



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