|
1 | | -use buffa::{Message, MessageView}; |
| 1 | +use buffa::{Message, MessageView, ViewEncode}; |
2 | 2 | use criterion::{criterion_group, criterion_main, Criterion, Throughput}; |
3 | 3 | use serde::{de::DeserializeOwned, Serialize}; |
4 | 4 |
|
@@ -189,6 +189,126 @@ fn bench_google_message1_view(c: &mut Criterion) { |
189 | 189 | group.finish(); |
190 | 190 | } |
191 | 191 |
|
| 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 | + |
192 | 312 | fn bench_api_response(c: &mut Criterion) { |
193 | 313 | benchmark_decode::<ApiResponse>( |
194 | 314 | c, |
@@ -267,6 +387,11 @@ criterion_group!( |
267 | 387 | bench_log_record_view, |
268 | 388 | bench_analytics_event_view, |
269 | 389 | 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, |
270 | 395 | ); |
271 | 396 |
|
272 | 397 | criterion_group!( |
|
0 commit comments