comphead commented on code in PR #7201:
URL: https://github.com/apache/datafusion-comet/pull/7201#discussion_r4417473746


##########
native/shuffle/benches/row_columnar.rs:
##########
@@ -559,6 +839,156 @@ fn benchmark_sorted_spill(c: &mut Criterion) {
     group.finish();
 }
 
+/// Map<Utf8, Int64> columns, at the entry counts of `map_conversion` so the 
two can be compared.
+///
+/// Keys go through the string path of the array reader: each one is read 
through an
+/// offset+size slot relative to the key array and appended to a 
`StringBuilder` one at a time,
+/// where Int64 keys are copied out in one slice. Keys are distinct within a 
map and vary from
+/// row to row, in length too, and values are distinct per row. Each fixture 
is converted and
+/// read back once, and checked against the entries it was built from, before 
it is timed.
+fn benchmark_utf8_map_conversion(c: &mut Criterion) {
+    let mut group = c.benchmark_group("map_utf8_conversion");
+
+    for num_entries in [10, 100] {
+        for num_rows in [1000, 10000] {
+            let schema = vec![make_utf8_map_schema()];
+            let entries: Vec<(Vec<String>, Vec<i64>)> = (0..num_rows)
+                .map(|row| {
+                    (0..num_entries)
+                        .map(|i| (format!("key_{i}_{}", row % 97), (row * 100 
+ i) as i64))
+                        .unzip()
+                })
+                .collect();
+            let rows: Vec<Vec<u8>> = entries
+                .iter()
+                .map(|(keys, values)| build_utf8_map_row(keys, values))
+                .collect();
+
+            let batch = convert_and_read_back(&schema, &rows, 1.0);
+            let maps = batch.column(0).as_map();
+            assert_eq!(maps.null_count(), 0);
+            for (row, (keys, values)) in entries.iter().enumerate() {
+                let entry = maps.value(row);
+                let actual_keys = entry.column(0).as_string::<i32>();
+                let actual_values = 
entry.column(1).as_primitive::<Int64Type>();
+                assert_eq!(entry.len(), num_entries, "row {row}");
+                for (i, (key, value)) in keys.iter().zip(values).enumerate() {
+                    assert_eq!(actual_keys.value(i), key, "row {row} key {i}");
+                    assert!(actual_values.is_valid(i), "row {row} value {i}");
+                    assert_eq!(actual_values.value(i), *value, "row {row} 
value {i}");
+                }
+            }
+
+            run_benchmark(
+                &mut group,
+                &format!("entries_{num_entries}"),
+                &format!("rows_{num_rows}"),
+                &schema,
+                &rows,
+                1,
+                1.0,
+            );
+        }
+    }
+
+    group.finish();
+}
+
+/// Primitive and map columns holding nulls. Every other case here is free of 
nulls, so it never
+/// reads a set null bit or appends a null.
+///
+/// - `cols_100_null_10pct`: the 100 Int64 columns of `primitive_columns`, 
with a tenth of the
+///   fields null. Each null is found through the row's null bitset and 
appended as a null.
+/// - `map_entries_{10,100}_value_null_10pct`: `Map<Int64, Int64>` with a 
tenth of the values
+///   null. The value array is read as nullable, and an array with a null 
skips the plain copy
+///   that a null-free array takes. At 10 entries about a third of the maps 
hold no null and the
+///   rest go element by element; at 100 entries nearly every map holds one 
and takes the copy
+///   with a validity buffer built from the null bitset (64 elements and up, 
in `list.rs`).
+///
+/// The nulls fall by a hash of row and position, values vary per row, and 
every row is built
+/// separately. Each fixture is converted and read back once, and checked null 
for null and value
+/// for value against how it was built, before it is timed.
+fn benchmark_nullable_conversion(c: &mut Criterion) {
+    let mut group = c.benchmark_group("nullable_conversion");
+    const NULL_FRACTION: f64 = 0.1;
+    const NUM_COLS: usize = 100;
+
+    for num_rows in [1000, 10000] {
+        let schema = vec![ArrowDataType::Int64; NUM_COLS];
+        let rows: Vec<Vec<u8>> = (0..num_rows)
+            .map(|row| build_primitive_row(row, NUM_COLS, NULL_FRACTION))

Review Comment:
   The nullable cases cannot be compared with the nearest null-free ones to get 
the cost of nulls, because the data differs too. `primitive_columns` writes the 
same byte pattern into every row, while this case writes distinct values per 
row, and `map_conversion` repeats one row while the nullable map rows vary by 
`row`. Zstd compression and the file write are inside the timed loop, so the 
gap mixes compressing distinct against repeated rows with the null handling. 
Since both builders already take the fraction, could each group add a 
`null_0pct` control on the same data, `build_primitive_row(row, NUM_COLS, 0.0)` 
and `build_map_row(num_entries, row, 0.0)`? I have not run the benchmark.



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