[EXPERIMENT] Pass buffer capacity and alignment to the allocator - #9812
[EXPERIMENT] Pass buffer capacity and alignment to the allocator#9812connortsui20 wants to merge 1 commit into
Conversation
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Merging this PR will degrade performance by 7.14%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Simulation | allocate_drop_vortex[64] |
3 µs | 7.6 µs | -61.2% |
| ❌ | Simulation | bitwise_and_vortex_buffer[128] |
4 µs | 8.7 µs | -54% |
| ❌ | Simulation | bitwise_or_vortex_buffer[128] |
4.1 µs | 8.8 µs | -53.32% |
| ❌ | Simulation | allocate_drop_vortex_custom[64] |
3.6 µs | 7.5 µs | -51.43% |
| ❌ | Simulation | bitwise_not_vortex_buffer[128] |
4.7 µs | 9.5 µs | -50.8% |
| ❌ | Simulation | allocate_freeze_drop_arrow[64] |
3.6 µs | 7.1 µs | -49.73% |
| ❌ | Simulation | bitwise_and_vortex_buffer[1024] |
4.3 µs | 8.4 µs | -49.15% |
| ❌ | Simulation | bitwise_and_vortex_buffer[2048] |
4.7 µs | 9.1 µs | -48.4% |
| ❌ | Simulation | allocate_freeze_drop_vortex_custom[64] |
4.3 µs | 8.3 µs | -48.03% |
| ❌ | Simulation | collect_bool_u32_gt[1024] |
10.7 µs | 14.2 µs | -24.95% |
| ❌ | Simulation | allocate_freeze_drop_vortex_custom[256] |
6.2 µs | 8.2 µs | -24.04% |
| ❌ | Simulation | allocate_freeze_drop_vortex[16384] |
6 µs | 7.7 µs | -21.45% |
| ❌ | Simulation | allocate_freeze_drop_vortex[65536] |
6 µs | 7.7 µs | -21.35% |
| ❌ | Simulation | new_raw_prim_test_between[i32, 2048] |
62 µs | 77.9 µs | -20.44% |
| ❌ | Simulation | allocate_drop_arrow[0] |
456.9 ns | 568 ns | -19.56% |
| ❌ | Simulation | allocate_freeze_drop_vortex[64] |
3.5 µs | 4.3 µs | -18.85% |
| ❌ | Simulation | random_i16[0.8] |
74.6 µs | 91.4 µs | -18.35% |
| ❌ | Simulation | allocate_drop_vortex[65536] |
6.4 µs | 7.9 µs | -18.02% |
| ❌ | Simulation | preverify_advancing_ptr_unchecked[1000] |
11.5 µs | 13.9 µs | -17.71% |
| ❌ | Simulation | allocate_drop_vortex[16384] |
6.4 µs | 7.8 µs | -16.98% |
| ... | ... | ... | ... | ... | ... |
ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing ct/buffer-allocation-boundary (22ef33a) with develop (402fd1d)
Footnotes
-
218 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
|
Linux x86_64 reproduces the main System allocation and growth regressions. Mimalloc results are mixed, and removing padding makes small aligned appends slower with both allocators. Compared baseline These selected cases use alignment 4096 and one live buffer. Times include drop and are the median of three process medians. Positive changes mean slower.
Separate instrumentation confirms the padding tradeoff: appending eight 64-byte chunks at alignment 4096 changes from one allocation to three with both allocators, while total requested bytes fall from 4160 to 832. Old usable capacity depends on the allocation address, so these counts describe the instrumented runs. The large macOS 64 KiB allocation spike did not reproduce. Linux System allocation/drop at alignment 4096 measured 24.39 to 37.04 ns in the full matrix and 25.12 to 39.27 ns in fresh processes. The regression persists, but without the roughly 13,473 ns macOS result. Other differences worth retaining:
The installed Rust System implementation routes the old alignment-1 requests through Benchmark methodology and validation
No repository code changed. Workspace tests were not rerun. These measurements do not establish application throughput, multithreaded behavior, RSS savings, or results on Intel, musl, or 32-bit x86. Small percentage changes need more evidence before treating them as regressions. The harness and raw samples are retained locally. Full Linux matrix, ns per operation
Fresh-process checks, ns per operation
|
Summary
Removes unconditional padding from
BufferMutallocation requests while preserving requested versus preferred alignment. Local macOS measurements show System allocation/drop and growth regressions, while mimalloc results are mixed. Linux testing will follow.Changes
Keeps the allocation base and backing layout separate from the data pointer and usable capacity, so slicing and logical alignment changes preserve the layout used for growth and deallocation. Extends the allocator tests with recorded layouts and forced moving growth, covering zeroed allocation, initialized spare capacity, sliced ownership, realignment, and empty buffers.
Correctness and validation
Layout { size: 4096, align: 4096 }, including zeroed allocation.Allocator::growcannot shrink.allocator-api20.2.21 sources.After rebasing onto
402fd1dfbd5c876ac7a4d24da3048bd69ac830d6, 892 crate tests and 14 doctests pass. The 26 allocator integration cases include the 10 layout-routing cases that failed before the original fix. Crate and workspace Clippy pass with all targets and features.cargo nextest run -p vortex-buffer --all-features cargo test --doc -p vortex-buffer --all-features cargo +nightly fmt --all rustfmt +nightly --edition 2024 --check vortex-buffer/src/allocation.rs vortex-buffer/src/buffer_mut.rs vortex-buffer/tests/allocation.rs cargo clippy -p vortex-buffer --all-targets --all-features cargo clippy --all-targets --all-features -- -D warnings git diff --checkThe changed files pass formatting. The workspace formatting command also ran, but its edits to four unrelated FFI/DuckDB files were discarded. The local commit hook still flags those existing formatting differences and was bypassed for this commit.
Existing macOS benchmark results and methodology
Measured baseline:
5c8c397e2297c41b81ca4aa77b0ca02c5d73728b. Measured candidate: the pre-rebase implementation, captured in local commit5fa15ee368c56d43ed7556e9a5d23e0a37385cdeand the retained candidate patch. These measurements isolate this change at the original base. They were not rerun after rebasing onto the upstream ZST and ownership changes, so they do not measure the final rebased commit.Both versions ran on the same Apple M4 Max with macOS 15.7.3, using Rust 1.98.0, LLVM 22.1.8,
aarch64-apple-darwin, optimization level 3, 16 codegen units, no LTO, full debug information, andRUSTFLAGS='-C force-frame-pointers=yes'. The standalone harness used identical locked dependencies and explicitly registered eitherstd::alloc::Systemormimalloc::MiMallocas the global allocator. The default buffer allocator forwards throughGlobal, so the named backend is selected by the benchmark binary. Mimalloc used wrapper 0.1.52,libmimalloc-sys0.1.49, and native mimalloc 3.3.2.The matrix covered 64, 4096, and 65536 bytes at effective alignments 256 and 4096, with immediate reuse or batches of 64 live buffers. Alignment 256 used requested alignment 1 and preferred alignment 256, matching normal
u8construction. Each operation includes drop:allocate: construct capacity N.zeroed: construct N zeroed bytes.grow: construct capacity N, initialize N bytes, then reserve an additional4 * max(N, 4096)bytes.append: construct capacity N, then append the same N-byte input eight times.Inputs and the batch container were allocated outside timing. Runtime sizes, mutable buffer references, and initialized growth contents passed through
black_box. Each case used adaptive warmup, then 11 samples of approximately 20 ms or longer, with three process runs alternating implementation order. Timings below are the median of the three process medians, in ns per complete buffer operation. No builds or correctness checks ran concurrently with these measurements.Requested-byte totals and usable capacities came from separate instrumented runs. Totals sum requests over one buffer lifetime, not peak memory or RSS. Old capacity depends on the allocation address, so the recorded capacities can differ from the timing processes. Small percentage changes do not establish statistical significance.
4096 bytes, requested alignment 4096
Initial allocation and zeroed requests change from
(8192, align 1)to(4096, align 4096). Forced growth ends with 20480 usable bytes in both versions, while the final backing request falls from 24576 to 20480 bytes.4096 bytes, normal preferred alignment 256
Initial allocation and zeroed requests change from
(4352, align 1)to(4096, align 256). Growth retains the preferred backing alignment while continuing to report requested alignment 1.Other regressions and context sensitivity
System immediate allocation/drop for 64 bytes at preferred alignment 256 increases from 27.60 to 70.15 ns, while the request falls from 320 to 64 bytes and recorded usable capacity falls from 160 to 64 bytes. Mimalloc increases from 8.36 to 10.56 ns for the same case, with recorded old capacity ranging from 128 to 320 bytes.
Mimalloc appending eight 64-byte chunks at alignment 4096 increases from 27.51 to 55.60 ns. The old implementation uses one 4160-byte request with 4160 usable bytes. The new implementation uses three requests totaling 832 bytes and ends at capacity 512. Losing incidental padding capacity can require more growth operations even when total requested bytes fall.
The 65536-byte, 4096-aligned System allocation/drop case is sensitive to benchmark context:
The request falls from 69632 to 65536 bytes, with the same recorded usable capacities. Immediate reuse regresses in both contexts, while the full-matrix batch improves. The large difference suggests allocator history matters, but its internal cause was not established. The checked Rust System implementation routes these aligned requests through
posix_memalign.A separate macOS System adapter experiment recovers much of the allocation/drop cost by retaining padding inside that specific allocator. For 4096 bytes at alignment 4096, it measures 17.68 ns versus 52.35 ns for the new native System path, but requests 8192 raw bytes while exposing only 4096 usable bytes. It also worsens zeroed batches from 113.74 to 151.49 ns and normal-preferred-alignment append batches from 792.17 to 1809.26 ns. The adapter is not included in this PR, and the System regressions remain.
These measurements do not establish Linux behavior, application throughput, or resident-memory savings. The harness, raw CSVs, and reproduction bundle are retained locally rather than attached to this PR.