Skip to content

Commit ea68c0d

Browse files
committed
benchmarks: encode_view + build_encode comparison
encode_view: serialize a pre-decoded view (parity with owned encode; wire-compat asserted by decode-and-compare). build_encode vs build_encode_view: construct a string+map LogRecord from borrowed source data and encode. Includes the per-field String allocs that the view path avoids — 1.35 µs → 227 ns (5.96×) on a 15-label fixture.
1 parent b2d6706 commit ea68c0d

File tree

2 files changed

+127
-1
lines changed

2 files changed

+127
-1
lines changed

benchmarks/buffa/benches/protobuf.rs

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use buffa::{Message, MessageView};
1+
use buffa::{Message, MessageView, ViewEncode};
22
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
33
use serde::{de::DeserializeOwned, Serialize};
44

@@ -189,6 +189,126 @@ fn bench_google_message1_view(c: &mut Criterion) {
189189
group.finish();
190190
}
191191

192+
/// Add `encode_view` to a concrete per-dataset bench group: pre-decode
193+
/// payloads into views, assert wire-compat against owned decode, then bench
194+
/// re-encoding from the views' borrowed fields. The owned `encode` baseline
195+
/// is in [`benchmark_decode`] — same group name, so throughputs sit side by
196+
/// side.
197+
///
198+
/// Per-dataset functions are concrete (not generic over `V`) because the
199+
/// views borrow from the locally-decoded `dataset.payload`; a `<'a, V>` fn
200+
/// signature can't tie `'a` to a local. Same shape as `decode_view` above.
201+
macro_rules! bench_view_encode {
202+
($fn_name:ident, $owned:ty, $view:ty, $group:literal, $dataset:literal) => {
203+
fn $fn_name(c: &mut Criterion) {
204+
let dataset = load_dataset(include_bytes!($dataset));
205+
let bytes = total_payload_bytes(&dataset);
206+
let views: Vec<$view> = dataset
207+
.payload
208+
.iter()
209+
.map(|p| <$view>::decode_view(p).unwrap())
210+
.collect();
211+
for (v, p) in views.iter().zip(&dataset.payload) {
212+
let from_view = <$owned>::decode_from_slice(&v.encode_to_vec()).unwrap();
213+
let from_wire = <$owned>::decode_from_slice(p).unwrap();
214+
assert!(from_view == from_wire, "view-encode wire mismatch");
215+
}
216+
let mut group = c.benchmark_group($group);
217+
group.throughput(Throughput::Bytes(bytes));
218+
group.bench_function("encode_view", |b| {
219+
b.iter(|| {
220+
for v in &views {
221+
criterion::black_box(v.encode_to_vec());
222+
}
223+
});
224+
});
225+
group.finish();
226+
}
227+
};
228+
}
229+
230+
bench_view_encode!(
231+
bench_api_response_view_encode,
232+
ApiResponse,
233+
ApiResponseView,
234+
"buffa/api_response",
235+
"../../datasets/api_response.pb"
236+
);
237+
bench_view_encode!(
238+
bench_log_record_view_encode,
239+
LogRecord,
240+
LogRecordView,
241+
"buffa/log_record",
242+
"../../datasets/log_record.pb"
243+
);
244+
bench_view_encode!(
245+
bench_analytics_event_view_encode,
246+
AnalyticsEvent,
247+
AnalyticsEventView,
248+
"buffa/analytics_event",
249+
"../../datasets/analytics_event.pb"
250+
);
251+
bench_view_encode!(
252+
bench_google_message1_view_encode,
253+
bench_buffa::proto3::GoogleMessage1,
254+
bench_buffa::proto3::GoogleMessage1View,
255+
"buffa/google_message1_proto3",
256+
"../../datasets/google_message1_proto3.pb"
257+
);
258+
259+
/// Build a `LogRecord` from borrowed source data and encode, vs build a
260+
/// `LogRecordView` from the same borrows and encode. Unlike `encode` /
261+
/// `encode_view` above (which serialize a pre-built struct), this includes
262+
/// the build phase — the per-field `String`/`HashMap` allocs that the view
263+
/// path avoids.
264+
fn bench_log_record_build_encode(c: &mut Criterion) {
265+
let labels: Vec<(&str, &str)> = (0..15)
266+
.map(|i| {
267+
(
268+
Box::leak(format!("k8s.io/label-key-{i:02}").into_boxed_str()) as &str,
269+
Box::leak(format!("label-value-{i:04}").into_boxed_str()) as &str,
270+
)
271+
})
272+
.collect();
273+
let service = "inventory-service-2a";
274+
let msg = "GET /api/v1/items?tenant=acme-corp&warehouse=us-west-2a&page=1400 200 17ms";
275+
let mut group = c.benchmark_group("buffa/log_record");
276+
let probe = LogRecord {
277+
service_name: service.into(),
278+
message: msg.into(),
279+
labels: labels.iter().map(|(k, v)| ((*k).into(), (*v).into())).collect(),
280+
..Default::default()
281+
}
282+
.encode_to_vec();
283+
group.throughput(Throughput::Bytes(probe.len() as u64));
284+
285+
group.bench_function("build_encode", |b| {
286+
b.iter(|| {
287+
let owned = LogRecord {
288+
service_name: service.into(),
289+
message: msg.into(),
290+
labels: labels.iter().map(|(k, v)| ((*k).into(), (*v).into())).collect(),
291+
..Default::default()
292+
};
293+
criterion::black_box(owned.encode_to_vec())
294+
});
295+
});
296+
297+
group.bench_function("build_encode_view", |b| {
298+
b.iter(|| {
299+
let view = LogRecordView {
300+
service_name: service,
301+
message: msg,
302+
labels: labels.iter().copied().collect(),
303+
..Default::default()
304+
};
305+
criterion::black_box(view.encode_to_vec())
306+
});
307+
});
308+
309+
group.finish();
310+
}
311+
192312
fn bench_api_response(c: &mut Criterion) {
193313
benchmark_decode::<ApiResponse>(
194314
c,
@@ -267,6 +387,11 @@ criterion_group!(
267387
bench_log_record_view,
268388
bench_analytics_event_view,
269389
bench_google_message1_view,
390+
bench_api_response_view_encode,
391+
bench_log_record_view_encode,
392+
bench_analytics_event_view_encode,
393+
bench_google_message1_view_encode,
394+
bench_log_record_build_encode,
270395
);
271396

272397
criterion_group!(

benchmarks/buffa/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ fn main() {
77
])
88
.includes(&["../proto/"])
99
.generate_json(true)
10+
.view_encode(true)
1011
.compile()
1112
.expect("failed to compile benchmark protos");
1213
}

0 commit comments

Comments
 (0)