Repository navigation
bench: add string, Map<Utf8, Int64> and nullable row-to-columnar cases (#7080) - #7201
Open
HarshilMital wants to merge 1 commit into
Open
HarshilMital wants to merge 1 commit into
HarshilMital wants to merge 1 commit into
Conversation
apache#7080) Part of apache#7080: the "Row to Arrow" item (strings, Map<Utf8, _> and nullable data). List cases are left out on purpose: apache#6855 asks that the list_conversion/elements_100 anomaly be understood before any new List case is added here. What's added to row_columnar (all through process_sorted_row_partition): - string_conversion: 10 Utf8 columns at 1000 and 10000 rows. - plain: prefer_dictionary_ratio 1.0, StringBuilder. - dictionary: ratio 10.0 (Comet's default) over 16 distinct values per column, so the output stays Dictionary(Int32, Utf8). - dictionary_fallback: ratio 10.0 over distinct values, so the column is built as a dictionary and cast back to plain, which is what a high-cardinality string column costs under the default. - map_utf8_conversion: Map<Utf8, Int64> at 10 and 100 entries and 1000 and 10000 rows, the same grid as map_conversion so the two compare directly. Keys go through the string arm of the UnsafeArrayData reader, against the bulk slice copy that Int64 keys take. - nullable_conversion: - cols_100_null_10pct: 100 Int64 fields with 10% null. - map_entries_{10,100}_value_null_10pct: Map<Int64, Int64> with 10% of values null. At 10 entries maps mix the plain copy and the per-element loop; at 100 nearly every map takes the copy plus a validity buffer built from the null bitset (64 elements and up). Nulls fall by a hash of (row, position), so they neither line up nor recur at a fixed period. All values vary per row and every row is built separately. Fixture builders: - build_string_row: offset/size pointers in the fixed region, bytes 8-byte aligned in the variable region. - build_utf8_map_row: key array as [count][null bitset][8-byte offset/size slots][8-byte aligned bytes], offsets relative to the key array start, as SparkUnsafeObject::get_string reads them. - build_primitive_row: Int64 fields with a null fraction. Helper changes: - run_benchmark takes prefer_dictionary_ratio instead of hardcoding 1.0; existing cases pass 1.0. - run_benchmark no longer calls set_not_null_at on every field. Every builder already zeroes its bitset, and clearing would erase the nullable fixtures' null bits. - build_map_row takes a row offset and a value null fraction; the existing map cases pass (0, 0.0), which reproduces their previous bytes exactly. Fixture check: before timing, every new fixture is run through process_sorted_row_partition, read back with read_ipc_compressed, and compared value for value (and null for null, and output type for the dictionary cases) against what was written. Reproduce on the VM (from native/): cargo bench -p datafusion-comet-shuffle --bench row_columnar -- 'string_conversion|map_utf8_conversion|nullable_conversion' --save-baseline main Results: TODO (VM)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue does this PR close?
Part of #7080.
Rationale for this change
row_columnaronly covered Int64 primitives, structs,List<Int64>andMap<Int64, Int64>, with no nulls. Strings are the most common shuffled type and take a different path (offset/size pointers into the variable-length region, and an optional dictionary builder); map keys are often strings; and real data has nulls. List cases are left out on purpose: #6855 asks that thelist_conversion/elements_100anomaly be understood before new List cases are added here.What changes are included in this PR?
All through
process_sorted_row_partition:string_conversion: 10 Utf8 columns.plain:prefer_dictionary_ratio1.0.dictionary: ratio 10.0 (Comet's default) over 16 distinct values per column, so the output staysDictionary(Int32, Utf8).dictionary_fallback: ratio 10.0 over distinct values, so the column is built as a dictionary and cast back to plain, which is what a high-cardinality string column costs under the default.map_utf8_conversion:Map<Utf8, Int64>at 10 and 100 entries, the same grid asmap_conversion.nullable_conversion: 100 Int64 fields with 10% null, andMap<Int64, Int64>with 10% of values null at 10 and 100 entries. Nulls fall by a hash of row and position.build_string_row,build_utf8_map_row(string slots relative to the key array start, asSparkUnsafeObject::get_stringreads them) andbuild_primitive_row.run_benchmarktakesprefer_dictionary_ratioinstead of hardcoding 1.0, and no longer callsset_not_null_aton every field (every builder already zeroes its bitset, and clearing would erase the new null bits).build_map_rowtakes a row offset and a value null fraction; the existing map cases pass(0, 0.0), which reproduces their previous bytes exactly.Results
Machine: 8 vCPU Arm Neoverse-V2 (aarch64), 31 GiB RAM, rustc 1.99.1
Commit:
d66cbba. Median with Criterion's 95% interval.Reproduce from
native/:cargo bench -p datafusion-comet-shuffle --bench row_columnar -- 'string_conversion|map_utf8_conversion|nullable_conversion' --save-baseline mainUnder the default ratio, a high-cardinality string column costs 43% more than building it plain (5.32 ms against 3.72 ms at 10,000 rows); a low-cardinality one is 54% cheaper (1.71 ms). Every case scales about linearly from 1,000 to 10,000 rows. All intervals are within ±0.1%.
How are these changes tested?
This PR only adds benchmark code under
native/shuffle/benches/; no production code changes.cargo fmt --all -- --checkandcargo clippy -p datafusion-comet-shuffle --all-targets -- -D warningspass.cargo bench -p datafusion-comet-shuffle --bench row_columnar --no-runbuilds.cargo bench -p datafusion-comet-shuffle --bench row_columnar -- --test 'string_conversion|map_utf8_conversion|nullable_conversion'.process_sorted_row_partition, read back withread_ipc_compressed, and compared value for value (null for null, and output type for the dictionary cases). Corrupting a string pointer locally makes this panic before any timing.row_columnarbench (50 cases, including the sorted-spill cases) also runs in test mode, sincerun_benchmarkandbuild_map_rowchanged.