close
Skip to content

perf: minimize allocation in min/max emit_to(First(n))#22416

Open
RyanJamesStewart wants to merge 1 commit into
apache:mainfrom
RyanJamesStewart:perf/min-max-emit-min-alloc
Open

perf: minimize allocation in min/max emit_to(First(n))#22416
RyanJamesStewart wants to merge 1 commit into
apache:mainfrom
RyanJamesStewart:perf/min-max-emit-min-alloc

Conversation

@RyanJamesStewart
Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Related to #22164 and #22165. This extends the same fix to the min/max accumulators.

Rationale for this change

#22165 fixed an allocation problem in EmitTo::First(n): drain(..n).collect() always allocates n elements and leaves the retained buffer at its pre-emit capacity, so an OOM-triggered emit (where n is close to the buffer length) ends up copying the largest allocation.

The same idiom is still present in two accumulators that #22165 did not touch:

  • MinMaxStructAccumulator::emit_to in datafusion/functions-aggregate/src/min_max/min_max_struct.rs
  • MinMaxBytesAccumulator::emit_to in datafusion/functions-aggregate/src/min_max/min_max_bytes.rs

Both emit a prefix of their min_max group buffer with self.min_max.drain(..n).collect().

What changes are included in this PR?

Adds a split_vec_min_alloc helper in min_max.rs that allocates min(n, len - n) by choosing drain+collect or split_off+replace depending on which side is smaller, and routes both accumulators through it.

The helper is duplicated from datafusion-physical-plan's split_vec_min_alloc (added in #22165) because that one is pub(super)-scoped to a different crate. It is a generic Vec<T> utility with no aggregate-specific logic. If maintainers prefer, it could instead be hoisted into datafusion-common and shared by both crates; happy to do that in this PR or as a follow-up. I went with the local copy to keep the change minimal and avoid touching recently merged code.

Are these changes tested?

Yes. Unit tests cover both branches of the helper plus the n == len and n == 0 boundaries. Behavior of emit_to is unchanged. cargo test -p datafusion-functions-aggregate passes (128 tests).

Are there any user-facing changes?

No.

`MinMaxStructAccumulator` and `MinMaxBytesAccumulator` both emit a
prefix of their `min_max` group buffer with `drain(..n).collect()`.
That always allocates `n` elements and leaves the retained buffer at
its pre-emit capacity. Under an OOM-triggered `EmitTo::First(n)` emit,
`n` is close to the buffer length, so this copies the largest
allocation -- the same problem apache#22165 fixed for the multi-group-by
`GroupColumn` builders.

This applies the same fix: a `split_vec_min_alloc` helper that
allocates `min(n, len - n)` by choosing `drain+collect` or
`split_off+replace` depending on which side is smaller. Both min/max
accumulators route through it.

The helper is duplicated from `datafusion-physical-plan`'s
`split_vec_min_alloc` (added in apache#22165) because that one is
`pub(super)`-scoped to a different crate. It is a generic `Vec<T>`
utility with no aggregate-specific logic; if maintainers prefer, it
could instead be hoisted into `datafusion-common` and shared by both
crates -- happy to do that here or as a follow-up.

Behavior is unchanged. Adds unit tests covering both branches and the
n == len / n == 0 boundaries.
@github-actions github-actions Bot added the functions Changes to functions implementation label May 21, 2026
/// Mirrors `split_vec_min_alloc` in `datafusion-physical-plan`'s
/// `aggregates::group_values::multi_group_by` module (added in #22165); kept
/// local here because that helper is `pub(super)` to a different crate.
fn split_vec_min_alloc<T>(vec: &mut Vec<T>, n: usize) -> Vec<T> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move this in datafusion-common

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have another use case for it in datafusion/expr-common/src/groups_accumulator.rs so it would be nice to have it in one place

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

functions Changes to functions implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants