Skip to content

feat(ipc): Avoid zero-filling IPC reads with typed buffer handling#9971

Open
pchintar wants to merge 1 commit into
apache:mainfrom
pchintar:ipc-typed-buffer-reads
Open

feat(ipc): Avoid zero-filling IPC reads with typed buffer handling#9971
pchintar wants to merge 1 commit into
apache:mainfrom
pchintar:ipc-typed-buffer-reads

Conversation

@pchintar
Copy link
Copy Markdown
Contributor

@pchintar pchintar commented May 14, 2026

Which issue does this PR close?

Rationale for this change

This PR is a follow-up to the alignment concerns raised in #9778 when using Vec<u8> for IPC body reads to replace the current MutableBuffer::from_len_zeroed in IPC Reader.

My earlier approach showed that reading directly into Vec<u8> could substantially reduce redundant zero-filling in IPC reader paths, but some decode paths still relied on fixed-width typed buffers that could require additional alignment handling cost later during array construction.

This PR keeps the Vec<u8>-based read path for IPC message and block bodies, while adding typed IPC buffer handling for fixed-width physical buffers before array construction.

This preserves the existing alignment behavior for those fixed-width decode paths while avoiding the additional alignment handling/copying costs that could otherwise occur later during array construction.

The typed-buffer handling now covers:

  • primitive and primitive-like arrays
  • binary/string offset buffers
  • list and list-view offsets/sizes
  • dictionary index buffers
  • view buffers

These paths now read their physical buffers through next_typed_buffer::<T>() so the expected physical buffer lengths are derived from the native value type before array construction.

Exception Case: UnionArray requires ScalarBuffer inputs directly, and external IPC producers may provide unaligned union type id or offset buffers. The updated Union handling therefore preserves the aligned fast path while falling back to aligned ScalarBuffer construction only for unaligned external union buffers.

Container types such as Struct, FixedSizeList, RunEndEncoded, and similar nested/container arrays were intentionally left on their existing decode paths because they do not directly own fixed-width value buffers at that level. Their child arrays continue to decode recursively through the updated typed-buffer paths where applicable.

Are these changes tested?

Yes.

The existing IPC reader test suite was run with:

cargo test -p arrow-ipc --lib

IPC reader benchmark was also run with:

cargo bench -p arrow-ipc --bench ipc_reader --features zstd

The non-compressed, non-mmap IPC reader paths showed consistent improvements locally. Compressed and mmap-heavy paths were mostly neutral, as expected.

Are there any user-facing changes?

No.

@github-actions github-actions Bot added the arrow Changes to the arrow crate label May 14, 2026
@pchintar pchintar force-pushed the ipc-typed-buffer-reads branch from b59bbe2 to e8845d0 Compare May 14, 2026 02:28
@pchintar pchintar changed the title Avoid zero-filling IPC reads with typed buffer handling feat(ipc): Avoid zero-filling IPC reads with typed buffer handling May 14, 2026
@pchintar pchintar force-pushed the ipc-typed-buffer-reads branch 2 times, most recently from a24d808 to 292cb21 Compare May 14, 2026 03:55
@pchintar pchintar force-pushed the ipc-typed-buffer-reads branch from 292cb21 to cabe3f2 Compare May 14, 2026 05:56
@pchintar
Copy link
Copy Markdown
Contributor Author

@alamb and @adriangb could you pls run benchmark ipc_reader

@Dandandan
Copy link
Copy Markdown
Contributor

run benchmark ipc_reader

@adriangbot
Copy link
Copy Markdown

🤖 Arrow criterion benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c4448280608-73-znvcv 6.12.68+ #1 SMP Wed Apr 1 02:23:28 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing ipc-typed-buffer-reads (cabe3f2) to 1ffd202 (merge-base) diff
BENCH_NAME=ipc_reader
BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental,object_store --bench ipc_reader
BENCH_FILTER=
Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot
Copy link
Copy Markdown

🤖 Arrow criterion benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                                                       ipc-typed-buffer-reads                 main
-----                                                       ----------------------                 ----
arrow_ipc_reader/FileReader/no_validation/read_10           1.00     86.8±0.47µs        ? ?/sec    1.43    124.4±5.17µs        ? ?/sec
arrow_ipc_reader/FileReader/no_validation/read_10/mmap      1.02     57.9±0.39µs        ? ?/sec    1.00     56.6±0.37µs        ? ?/sec
arrow_ipc_reader/FileReader/read_10                         1.00   377.9±43.54µs        ? ?/sec    1.08   407.7±52.57µs        ? ?/sec
arrow_ipc_reader/FileReader/read_10/mmap                    1.00   462.4±54.58µs        ? ?/sec    1.01   467.7±53.99µs        ? ?/sec
arrow_ipc_reader/StreamReader/no_validation/read_10         1.00     87.3±0.83µs        ? ?/sec    1.41    123.0±1.75µs        ? ?/sec
arrow_ipc_reader/StreamReader/no_validation/read_10/zstd    1.00      2.4±0.02ms        ? ?/sec    1.00      2.4±0.02ms        ? ?/sec
arrow_ipc_reader/StreamReader/read_10                       1.00   355.4±54.52µs        ? ?/sec    1.14   406.6±55.70µs        ? ?/sec
arrow_ipc_reader/StreamReader/read_10/zstd                  1.00      2.7±0.03ms        ? ?/sec    1.00      2.7±0.07ms        ? ?/sec

Resource Usage

base (merge-base)

Metric Value
Wall time 85.0s
Peak memory 2.7 GiB
Avg memory 2.6 GiB
CPU user 71.7s
CPU sys 10.3s
Peak spill 0 B

branch

Metric Value
Wall time 90.0s
Peak memory 2.7 GiB
Avg memory 2.6 GiB
CPU user 76.2s
CPU sys 9.7s
Peak spill 0 B

File an issue against this benchmark runner

Copy link
Copy Markdown
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

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

Thanks @pchintar -- this is getting closer but I think this still may cause issues.

I think any time we allocate (or reallcoate) a Vec<u8> we lose the alignment guarantee's. There are still several places in this PR that do Vec::new(...)

Poking around I found a way to to get an aligned vector that maybe we can try here

https://stackoverflow.com/questions/60180121/how-do-i-allocate-a-vecu8-that-is-aligned-to-the-size-of-the-cache-line

So instead of this code

    let mut buf = MutableBuffer::from_len_zeroed(total_len);
    reader.read_exact(&mut buf)?;
    Ok(buf.into())

We could do something like

    let mut buf = aligned_vec(total_len);
    reader.read_exact(&mut buf)?;
    Ok(buf.into())

And as long as aligned_vec was aligned to (1<<6) (64) I think it would be equivalent to the current mutable buffer. Maybe we should align to 128 to guarantee views work 🤔

Comment thread arrow-ipc/src/reader.rs
/// reads.
enum IpcBufferSource<'a> {
Buffer(&'a Buffer),
}
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.

Stylistically if you want to make a wrapper type I think a more common pattern is a an "new type" struct -- something like

struct IpcBufferSource<'a>(&'a Buffer)

That way you can still define methods on it, but you don't have to use match all over the place - you can just refer to the inner field as .0

Comment thread arrow-ipc/src/reader.rs
compression: Option<CompressionCodec>,
decompression_context: &mut DecompressionContext,
) -> Result<Buffer, ArrowError> {
let byte_len = len
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 found the naming a little confusing here as there are three buffers

I found this a little confusing as there three buffers -- self::Buffer, buf (the input buffer) and the output buffer

Can we maybe name them something more specific to distinguish them? I am in particular confused about the buffer in self and the one passed in via the argument

Comment thread arrow-ipc/src/reader.rs
ArrowError::IpcError("Buffer count mismatched with metadata".to_string())
})?;

self.data.read_typed_buffer::<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.

this part looks good, so far

Comment thread arrow-ipc/src/reader.rs
// Some invalid or legacy IPC inputs may contain shorter buffers than
// implied by the schema. Preserve the existing behavior and let array
// construction/validation report the error.
if buffer.len() <= byte_len || buffer.as_ptr().align_offset(std::mem::align_of::<T>()) != 0
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.

This doens't seem right -- our change should ensure that the buffer is always aligned correctly to the otutput requirements. The fact you have to check afterwards suggests this is not the case

Comment thread arrow-ipc/src/reader.rs
buf.len()
)));
}
Ok(Buffer::from_vec(buf))
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 think this Vec and the one below (still) has no guaranteed alignment.

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

Labels

arrow Changes to the arrow crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove redundant zero-initialization in Arrow IPC reader hot paths

4 participants