When should I collect eBPF samples in a BPF ring buffer instead of a perf event array?
Short answer: a perf event array and a BPF ring buffer move eBPF samples from
kernel to user space with different ownership models. A perf event
(BPF_MAP_TYPE_PERF_EVENT_ARRAY) is per-CPU: one perf fd and a fixed-size data
region per CPU, consumed per-CPU, and each CPU drops samples independently when
its own region fills. A BPF ring buffer (BPF_MAP_TYPE_RINGBUF) is a shared
multiproducer/singleconsumer (MPSC) ring with no perf fd, consumed once, where
bpf_ringbuf_reserve() writes zero-copy into one ring shared across CPUs. The
decisive boundary is who the producers are. If producers and the consumer live
on the same CPU, or you want per-CPU isolation with a fixed-size data region, a
perf event array is the right tool. If producers span many CPUs or threads and
you want one shared consumption stream without a perf fd per CPU, use the ring
buffer. A ring buffer can drop a record a perf array keeps: a ring buffer
reservation fails atomically for the shared ring when it is full, and even
while there is free space it can fail in NMI context while contending for the
ring lock; a perf event array overflow is bounded per-CPU, so each CPU's region
simply stops accepting new samples on its own.
The two ownership models
A perf event array has one slot per CPU. Each slot is a perf buffer with a fixed-size data region and one perf fd. The consumer reads each CPU's buffer independently. When a CPU's region overflows, only that CPU stops accepting new samples; every other CPU keeps going. Nothing is shared between CPUs except the map, so a burst on one CPU cannot starve another CPU's samples.
A ring buffer is the opposite. A single BPF_MAP_TYPE_RINGBUF map presents one
power-of-2 sized ring to all CPUs (a pool of rings is available through
BPF_MAP_TYPE_HASH_OF_MAPS for sharded designs). Producers on any CPU reserve
space in the same ring; one consumer drains it. There is no perf fd and no
per-CPU region. The kernel documents the motivation directly: a shared ring
across CPUs gives better memory utilization and removes the per-CPU perf
buffers that perf event arrays require.
The helper API is where the difference shows up. bpf_perf_event_output()
copies a record into the per-CPU perf buffer. bpf_ringbuf_output() does the
same copy into the ring buffer. bpf_ringbuf_reserve() /
bpf_ringbuf_commit() / bpf_ringbuf_discard() instead hand the program a
pointer directly into the ring, so the program writes the sample in place with
no copy. bpf_ringbuf_query() reports BPF_RB_AVAIL_DATA (unconsumed bytes)
and BPF_RB_RING_SIZE, which is what a consumer uses to size and pace itself.
Failure modes are not the same
The drop behavior differs in shape, which is the point most people miss:
- Perf event array overflow is per-CPU and independent. Each CPU's region fills on its own; that CPU's further samples are dropped while the others continue. There is a stable per-CPU backpressure model.
- Ring buffer overflow is global across the shared ring. When the shared
ring is full,
bpf_ringbuf_reserve()fails atomically and the sample is dropped for every producer at once. - Ring buffer reservation can fail even when not full. In NMI context,
bpf_ringbuf_reserve()may fail to acquire the ring lock, so the reservation fails despite free space. A program that samples in NMI or a tight atomic path must treat "not full" as "not guaranteed to succeed."
How to verify which one you need
- Locate the producers. If sampling runs on the same CPU that consumes, or you need per-CPU isolation, a per-CPU perf event array avoids the shared ring's global contention and its cross-CPU starvation.
- Watch the drop source, not just the count. For a ring buffer, poll
bpf_ringbuf_query(BPF_RB_AVAIL_DATA)before and after a load and confirm the consumer drains toward zero. For a perf event array, check each CPU's overflow independently. A ring buffer that holds high available data after a burst is dropping globally; a perf array will show the overflow only on the CPUs that were hot. - Check the producer context. If the program can run in NMI or a tight
atomic path, log
bpf_ringbuf_reserve()failures specifically. Free space does not guarantee a successful reservation there, so the copy-basedbpf_ringbuf_output()or reduced lock contention are the mitigations, not a larger ring. - Match the topology. If producers span many cores, the shared ring removes the per-CPU perf-fd overhead of a perf event array. If you already pin consumer threads to CPUs, a per-CPU perf event array lines up cleanly.
The limitation that decides it
A shared ring buffer trades per-CPU isolation for a single consumption stream. A burst on one CPU can fill the shared ring and drop records for every producer at once; there is no per-CPU backpressure in the shared design. A perf event array keeps that isolation: a burst on one CPU only drops that CPU's samples. Use the ring buffer when a shared stream and zero-copy reserve are worth accepting global overflow; stay with a perf event array when per-CPU isolation and a fixed per-CPU data region are the requirement.
References
- The Linux kernel documentation: BPF ring buffer
- perf_event_open(2)
- The Linux kernel documentation: BPF maps
Community discussion today
The monitored window was not technical. Over the previous 24 hours the two
opt-in Slack archives returned no messages; the 7-day fallback held only meeting
logistics — scheduling posts, a co-presenting thank-you, and a link to a
community session — with no eBPF question, symptom, or design dispute. The two
allowlisted chat workspaces had no visible browser session this run, and the
public mailing-list and forum archives were not reviewed. Those sources are
recorded as unavailable coverage, not quiet. Because the readable archive
yielded no question, this Q&A is grounded in public primary documentation
rather than a community message: the question is a recurring practitioner
decision — choosing between the BPF ring buffer and the perf event array for
moving samples to user space — verified against the kernel's ring buffer design
document and the perf_event_open(2) interface. No private text, identity,
channel, or link is reproduced here.
Continue exploring
Back to index
eBPF Q&A
Daily, source-grounded answers to recurring questions about eBPF, Linux observability, profiling, runtime extension, and security.
Previous
Why can't SIGKILL stop a BPF program load after verification has succeeded?
Short answer: the killable part of a load is only the main verification loop; the instruction-rewrite tail that runs after it has no cancellation points at all:
Next
Why does a kprobe on a function never fire when the compiler inlined it?
Short answer: a symbol-based kernel probe attaches only to a function that still exists as a standalone symbol at attach time.
- Last updated
- Sep 13, 2026
- First published
- Sep 13, 2026
- Contributors
- Yunwei 123
Was this page helpful?