Skip to content

perf: improve splitting of the offset vector in GroupColumn::take_n implementations, adding a new helper fn#23730

Open
mzabaluev wants to merge 3 commits into
apache:mainfrom
mzabaluev:take-n-offsets
Open

perf: improve splitting of the offset vector in GroupColumn::take_n implementations, adding a new helper fn#23730
mzabaluev wants to merge 3 commits into
apache:mainfrom
mzabaluev:take-n-offsets

Conversation

@mzabaluev

@mzabaluev mzabaluev commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Rationale for this change

The take_n implementation for ByteGroupValueBuilder uses split_vec_min_alloc to split the offset vector, then subtracts the n-th offset from the remaining offsets in place. This is slower than a single read-subtract-write pass.
The implementation in #23648 will benefit from the same optimization.

What changes are included in this PR?

Add a utility function named take_n_offsets in datafusion-common, which minimizes allocation in the way of split_vec_min_alloc, but also shifts the remaining offsets in a single pass with the copying.

Are these changes tested?

Added a micro-benchmark that shows improvement on the drain-like branch of the split, and no change on the split_off-like branch.
To keep results in context, the timed function is GroupValues::emit on a setup with two grouped columns, one of them of type Utf8, the other an integer.

Benchmark results (on a GCE instance type C4, Intel Xeon Emerald Rapids host CPU)
emit_first_small/shift_larger/i32_offsets_grp_1000
                        time:   [4.3658 µs 4.3738 µs 4.3808 µs]
                        change: [−4.6588% −3.2597% −1.9925%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 9 outliers among 200 measurements (4.50%)
  9 (4.50%) low mild
emit_first_small/shift_midsize/i32_offsets_grp_1000
                        time:   [6.1981 µs 6.2075 µs 6.2156 µs]
                        change: [−4.5298% −3.7144% −2.8939%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 14 outliers among 200 measurements (7.00%)
  14 (7.00%) low mild
emit_first_small/shift_smaller/i32_offsets_grp_1000
                        time:   [7.6934 µs 7.7011 µs 7.7078 µs]
                        change: [−1.1020% −0.5549% −0.0432%] (p = 0.04 < 0.05)
                        Change within noise threshold.
Found 17 outliers among 200 measurements (8.50%)
  16 (8.00%) low mild
  1 (0.50%) high mild
emit_first_small/shift_larger/i64_offsets_grp_1000
                        time:   [4.8437 µs 4.8529 µs 4.8608 µs]
                        change: [−7.9716% −6.5318% −5.0240%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 32 outliers among 200 measurements (16.00%)
  3 (1.50%) low severe
  29 (14.50%) low mild
emit_first_small/shift_midsize/i64_offsets_grp_1000
                        time:   [6.5677 µs 6.5802 µs 6.5915 µs]
                        change: [−6.8527% −5.7676% −4.6713%] (p = 0.00 < 0.05)
                        Performance has improved.
emit_first_small/shift_smaller/i64_offsets_grp_1000
                        time:   [7.7735 µs 7.7847 µs 7.7950 µs]
                        change: [−2.2958% −1.7079% −1.1464%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 2 outliers among 200 measurements (1.00%)
  2 (1.00%) high mild

emit_first_large/shift_larger/i32_offsets_grp_1000000
                        time:   [7.9151 ms 7.9374 ms 7.9629 ms]
                        change: [−5.4314% −5.0039% −4.5755%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 50 measurements (10.00%)
  3 (6.00%) high mild
  2 (4.00%) high severe
emit_first_large/shift_midsize/i32_offsets_grp_1000000
                        time:   [10.184 ms 10.205 ms 10.231 ms]
                        change: [−4.5136% −4.2636% −3.9524%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 2 outliers among 50 measurements (4.00%)
  2 (4.00%) high severe
emit_first_large/shift_smaller/i32_offsets_grp_1000000
                        time:   [7.8004 ms 7.8123 ms 7.8265 ms]
                        change: [−1.1698% −0.9187% −0.6655%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 3 outliers among 50 measurements (6.00%)
  1 (2.00%) high mild
  2 (4.00%) high severe
emit_first_large/shift_larger/i64_offsets_grp_1000000
                        time:   [8.7289 ms 8.7478 ms 8.7684 ms]
                        change: [−8.4380% −8.0534% −7.6539%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 4 outliers among 50 measurements (8.00%)
  3 (6.00%) high mild
  1 (2.00%) high severe
emit_first_large/shift_midsize/i64_offsets_grp_1000000
                        time:   [10.850 ms 10.865 ms 10.880 ms]
                        change: [−7.2120% −6.9233% −6.6671%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 1 outliers among 50 measurements (2.00%)
  1 (2.00%) high mild
emit_first_large/shift_smaller/i64_offsets_grp_1000000
                        time:   [8.1261 ms 8.1466 ms 8.1707 ms]
                        change: [−0.2579% +0.0712% +0.4146%] (p = 0.69 > 0.05)
                        No change in performance detected.
Found 3 outliers among 50 measurements (6.00%)
  1 (2.00%) high mild
  2 (4.00%) high severe

I set --turbo-mode=ALL_CORE_MAX in the instance parameters and pinned CPU cores to reduce variation.

Are there any user-facing changes?

utils::take_n_offsets function is added in datafusion-common.

@github-actions github-actions Bot added common Related to common crate physical-plan Changes to the physical-plan crate labels Jul 20, 2026
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.70%. Comparing base (3b1ce16) to head (3c2e3ba).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #23730      +/-   ##
==========================================
+ Coverage   80.69%   80.70%   +0.01%     
==========================================
  Files        1088     1089       +1     
  Lines      367766   368159     +393     
  Branches   367766   368159     +393     
==========================================
+ Hits       296781   297135     +354     
- Misses      53276    53316      +40     
+ Partials    17709    17708       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

A refinement of split_vec_min_alloc for splitting offset buffers.
To improve performance of the current use in `BytesGroupValues::take_n`,
this function adjusts the remaining offsets when they are copied to
the new location, rather than two passes of copying then subtracting
in place. It also accounts for n + 1 length of the resulting
prefix vector, which current split_vec_min_alloc falls on the wrong
side of.
Benchmark the performance of take_n for ByteGroupValueBuilder
in the context of the multi-group-by operation where it is used
in production.
This is faster and could be reused with other GroupColumn
implementations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

common Related to common crate physical-plan Changes to the physical-plan crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants