Skip to content

Commit 3dafa71

Browse files
committed
perf(memtrack): decide ring buffer wakeups producer-side
Every submit called bpf_ringbuf_query(BPF_RB_AVAIL_DATA) to decide whether to force a consumer wakeup. That reads the consumer position, a cache line the polling thread on another CPU writes continuously, so each event paid a cross-CPU miss for a decision that only changes once per watermark. Count submitted bytes per CPU instead and force a wakeup whenever the watermark is crossed. Events are fixed size, so this is the same cadence the query approximated, decided entirely on the producer side with no shared cache line involved. A missing counter forces the wakeup rather than risking a stalled consumer. Measured on a malloc/free latency harness (p50 per pair, glibc): 2064 ns to 1102 ns, the largest of the three hot-path wins. Verified at 10M malloc/free pairs (20,006,217 events, ~800 MB through the 256 MB ring buffer) with the dropped-event counter still at zero, so batched wakeups keep up with a sustained high event rate.
1 parent d0669a1 commit 3dafa71

2 files changed

Lines changed: 37 additions & 9 deletions

File tree

crates/memtrack/src/ebpf/c/utils/event_helpers.h

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,36 @@
88
BPF_RINGBUF(events, 256 * 1024 * 1024);
99
BPF_ARRAY_MAP(dropped_events, __u64, 1);
1010

11-
/* Wake the consumer only once this much unconsumed data has accumulated.
12-
* Per-event wakeups dominate submission cost at high event rates; batching
13-
* them behind a data watermark amortizes the wakeup to ~1 per thousand
14-
* events. The userspace poller's poll timeout flushes the tail that never
15-
* reaches the watermark. */
11+
/* Wake the consumer once this much data has been submitted. Per-event wakeups
12+
* dominate submission cost at high event rates; batching them behind a data
13+
* watermark amortizes the wakeup to ~1 per thousand events. The userspace
14+
* poller's poll timeout flushes a tail that never reaches the watermark. */
1615
#define WAKEUP_DATA_SIZE (64 * 1024)
1716

18-
static __always_inline long wake_flags(void) {
19-
long avail = bpf_ringbuf_query(&events, BPF_RB_AVAIL_DATA);
20-
return avail >= WAKEUP_DATA_SIZE ? BPF_RB_FORCE_WAKEUP : BPF_RB_NO_WAKEUP;
17+
/* Bytes submitted per CPU since the last forced wakeup.
18+
*
19+
* Counting what this CPU produced, rather than asking the ring buffer how much
20+
* is unconsumed, keeps the decision on the producer side: bpf_ringbuf_query()
21+
* reads the consumer position, a cache line the polling thread on another CPU
22+
* writes continuously, so querying it per event costs a cross-CPU miss on every
23+
* event. */
24+
BPF_PERCPU_ARRAY_MAP(submitted_bytes, __u64, 1);
25+
26+
static __always_inline long wake_flags(__u64 event_size) {
27+
__u32 zero = 0;
28+
__u64* pending = bpf_map_lookup_elem(&submitted_bytes, &zero);
29+
if (!pending) {
30+
/* Can't track the watermark, so don't risk a stalled consumer. */
31+
return BPF_RB_FORCE_WAKEUP;
32+
}
33+
34+
*pending += event_size;
35+
if (*pending < WAKEUP_DATA_SIZE) {
36+
return BPF_RB_NO_WAKEUP;
37+
}
38+
39+
*pending = 0;
40+
return BPF_RB_FORCE_WAKEUP;
2141
}
2242

2343
/* Per-thread scratch for the allocator entry/exit hand-off.
@@ -144,7 +164,7 @@ static __always_inline struct memtrack_task_state* take_slot(enum arg_slot slot)
144164
\
145165
fill_data; \
146166
\
147-
bpf_ringbuf_submit(e, wake_flags()); \
167+
bpf_ringbuf_submit(e, wake_flags(sizeof(*e))); \
148168
return 0; \
149169
}
150170

crates/memtrack/src/ebpf/c/utils/map_helpers.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@
2828
__type(value, value_type); \
2929
} name SEC(".maps")
3030

31+
#define BPF_PERCPU_ARRAY_MAP(name, value_type, max_ents) \
32+
struct { \
33+
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); \
34+
__uint(max_entries, max_ents); \
35+
__type(key, __u32); \
36+
__type(value, value_type); \
37+
} name SEC(".maps")
38+
3139
#define BPF_RINGBUF(name, size) \
3240
struct { \
3341
__uint(type, BPF_MAP_TYPE_RINGBUF); \

0 commit comments

Comments
 (0)