Skip to content

[Epic] Replace OffsetBufferBuilder / BufferBuilder usage with Vec, when possible #10245

Closed

Description

@alamb

Is your feature request related to a problem or challenge?

As @Jefffrey has observed in #10229 (comment), switching from OffsetBufferBuilder / BufferBuilder to use Vec<..> often results in a non trivial speedup.

This is likely because the Rust language has a highly optimized Vec implementation

Describe the solution you'd like

Basically try and change any code like this

    let mut values = BufferBuilder::<T::Native>::new(len);

To use

let mut values = Vec<T::Native>::with_capacity(len);

And various equivalent

Note to contributors:

please make targeted PRs (that change one or two usages each) to keep the PRs easy to review and thus fast to merge

Remaining callsites

(as of 0e581556)

Describe alternatives you've considered

No response

Additional context

No response

Activity

  1. added theissue type on Jun 30, 2026
  2. comphead commented on Jun 30, 2026

    @comphead
    Contributor

    Thanks @alamb adding apache/datafusion#23195 as the reference where Vec replaces OffsetBufferBuilder

  3. Rich-T-kid commented on Jul 15, 2026

    @Rich-T-kid
    Contributor

    take

  4. Rich-T-kid commented on Aug 4, 2026

    @Rich-T-kid
    Contributor

    @mzabaluev @Anakin100100 are either of you currently working on this? Im working on a few other issues so im happy to untake, otherwise Ill push up a PR for this tomorrow

  5. mzabaluev commented on Aug 4, 2026

    @mzabaluev
    Contributor

    @Rich-T-kid I don't have anything pending on this, working on other things now.

  6. cakeni commented on Aug 11, 2026

    @cakeni
    Contributor

    I'd like to take the remaining BufferBuilder usage in arrow-json's MapArrayDecoder and replace it with Vec, following the existing ListArrayDecoder approach.

  7. cakeni commented on Aug 11, 2026

    @cakeni
    Contributor

    I'd like to take the remaining BufferBuilder usage in arrow-ord/src/sort.rs and replace it with Vec.

  8. cakeni commented on Aug 11, 2026

    @cakeni
    Contributor

    I'd like to take the two BufferBuilder usages in arrow-select/src/take.rs's run-end encoded take path and replace them with Vec.

  9. cakeni commented on Aug 11, 2026

    @cakeni
    Contributor

    I'd like to take the remaining BufferBuilder usages in arrow-string and replace them with Vec, split into focused PRs for concat_elements.rs and substring.rs.

  10. Rich-T-kid commented on Aug 11, 2026

    @Rich-T-kid
    Contributor

    nice, i took a look at the existing PR's

  11. 69 remaining items

  12. WaterWhisperer commented on Nov 7, 2026

    @WaterWhisperer
    Contributor

    I updated the checklist in the description to reflect current main:

    Once those two PRs are merged (or otherwise resolved) we can close this epic 🎉

    I looked into the remaining nullable try_binary callsite referenced in #10518.

    The local patch only replaces BufferBuilder with vec![O::Native::default(); len] and converts the result with .into().

    A focused benchmark on base commit showed repeatable regressions in some cases. It calls try_binary directly with checked addition, fixed seeded inputs, and nulls on the left input only. Input generation is outside the timed operation.

    Environment: Intel i7-13700H, Linux x86_64, Rust 1.99.1, glibc 2.39, default release settings and system allocator. Both versions were pinned to the same CPU core, with the powersave governor unchanged. Each case was measured six times per version, alternating execution order and shuffling case order between rounds, using 100 Criterion samples and a 2-second measurement target per run.

    Selected results below are average execution times across the six runs:

    • Decimal256, 8,192 rows, 10% nulls: 30.03 → 33.33 µs, 11.0% slower.
    • Decimal256, 65,536 rows, 10% nulls: 284.00 → 305.35 µs, 7.5% slower.
    • Decimal256, 1,048,576 rows, 10% nulls: 17.42 → 17.68 ms, 1.4% slower.
    • Int32, 128 rows, all null: 87.64 → 101.36 ns, 15.7% slower.

    For Decimal256 with 50% nulls, the corresponding changes at those three batch sizes were +10.3%, +6.5%, and +0.8%. Larger Int32 cases were generally close to unchanged.

    The Decimal256 regressions at 8,192 and 65,536 rows occurred in all six comparisons. At one million rows, the differences were much smaller. One unchanged no-null Decimal256 control also showed an approximately 10% swing in one comparison, so I would treat small differences cautiously.

    These results are limited to this machine, toolchain, and workload. The nullable benchmark mentioned in #10518 was not included in that PR, so I cannot compare the two setups directly or explain its reported gains conclusively.

    Would it make sense to keep BufferBuilder at this callsite for now, pending results from other environments or a replacement with more consistent benefits? I have retained the focused benchmark and raw results locally and can share them for reproduction.

  13. alamb commented on Nov 7, 2026

    @alamb
    ContributorAuthor

    Would it make sense to keep BufferBuilder at this callsite for now, pending results from other environments or a replacement with more consistent benefits? I have retained the focused benchmark and raw results locally and can share them for reproduction.

    Yes I agree -- thank you @WaterWhisperer

    Let's call this done for now

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions