Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changes/unreleased/breaking-changes-20260627-002930.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
kind: Breaking changes
body: |-
Singular message fields are now stored inline by default: codegen emits `MessageField<T, ::buffa::Inline<T>>` (laid out as `Option<T>`, no per-field heap allocation) for every non-recursive field. Recursive fields are detected and stay on `Box` automatically. (#248)

To restore the old behaviour for specific fields (e.g. large or rarely-set submessages), use `box_type_in(PointerRepr::Box, &[".pkg.Msg.field"])`; for the old global default, `box_type(PointerRepr::Box)`. Explicit `MessageField<Foo>` type annotations now mean the boxed form and will mismatch the new default — drop the annotation and let `P` infer from the field's declared type (or, for a standalone value with no pinning context, write `MessageField::<Foo, buffa::Inline<Foo>>::some(x)`).

`box_type_in` / `box_type_custom_in` now normalize a missing leading dot on each path; previously a dotless path silently matched nothing.
time: 2026-06-27T00:29:30.000000000Z
2 changes: 1 addition & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ if msg.address.is_set() { ... }
msg.address.get_or_insert_default().street = "123 Main St".into();
```

`MessageField<T>` is heap-allocated (`Option<Box<T>>` internally) so the struct size stays small, but the Deref impl provides transparent read access through a lazily-initialized `&'static T` default singleton.
`MessageField<T, P>` stores the message inline by default (`Option<Inline<T>>` ≡ `Option<T>` — no per-field heap allocation), with recursive fields and explicit `box_type_in(PointerRepr::Box, …)` opt-outs falling back to `Option<Box<T>>`. The Deref impl provides transparent read access through a lazily-initialized `&'static T` default singleton.

### 4. EnumValue\<T\> — Type-Safe Open Enums

Expand Down
12 changes: 9 additions & 3 deletions benchmarks/buffa/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ buffa-build = { path = "../../buffa-build" }
criterion = "0.5"

[features]
default = ["api_response", "log_record", "analytics_event", "google_message1", "media_frame", "packed_tile", "reflect", "lazy"]
default = ["api_response", "log_record", "analytics_event", "google_message1", "media_frame", "packed_tile", "mesh", "reflect", "lazy"]
api_response = []
log_record = []
analytics_event = []
google_message1 = []
media_frame = []
packed_tile = []
mesh = []
reflect = []
lazy = []
# `iso` gates the per-message isolated targets out of the default `task bench` run.
Expand All @@ -38,12 +39,12 @@ iso = []
[[bench]]
name = "protobuf"
harness = false
required-features = ["api_response", "log_record", "analytics_event", "google_message1", "media_frame", "packed_tile", "lazy"]
required-features = ["api_response", "log_record", "analytics_event", "google_message1", "media_frame", "packed_tile", "mesh", "lazy"]

[[bench]]
name = "reflect"
harness = false
required-features = ["api_response", "log_record", "analytics_event", "google_message1", "media_frame", "packed_tile", "reflect", "lazy"]
required-features = ["api_response", "log_record", "analytics_event", "google_message1", "media_frame", "packed_tile", "mesh", "reflect", "lazy"]

[[bench]]
name = "api_response"
Expand Down Expand Up @@ -75,6 +76,11 @@ name = "packed_tile"
harness = false
required-features = ["iso", "packed_tile"]

[[bench]]
name = "mesh"
harness = false
required-features = ["iso", "mesh"]

# Build at the release profile (lto, single codegen unit) for a fair cross-impl
# comparison — the same profile a consumer building in release gets, and the one
# the per-release benchmark history pins. These crates are excluded from the root
Expand Down
1 change: 1 addition & 0 deletions benchmarks/buffa/benches/analytics_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
feature = "log_record",
feature = "media_frame",
feature = "packed_tile",
feature = "mesh",
feature = "google_message1",
feature = "reflect",
feature = "lazy"
Expand Down
1 change: 1 addition & 0 deletions benchmarks/buffa/benches/api_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
feature = "analytics_event",
feature = "media_frame",
feature = "packed_tile",
feature = "mesh",
feature = "google_message1",
feature = "reflect",
feature = "lazy"
Expand Down
1 change: 1 addition & 0 deletions benchmarks/buffa/benches/google_message1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
feature = "analytics_event",
feature = "media_frame",
feature = "packed_tile",
feature = "mesh",
feature = "reflect",
feature = "lazy"
))]
Expand Down
1 change: 1 addition & 0 deletions benchmarks/buffa/benches/log_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
feature = "analytics_event",
feature = "media_frame",
feature = "packed_tile",
feature = "mesh",
feature = "google_message1",
feature = "reflect",
feature = "lazy"
Expand Down
1 change: 1 addition & 0 deletions benchmarks/buffa/benches/media_frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
feature = "log_record",
feature = "analytics_event",
feature = "packed_tile",
feature = "mesh",
feature = "google_message1",
feature = "reflect",
feature = "lazy"
Expand Down
37 changes: 37 additions & 0 deletions benchmarks/buffa/benches/mesh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Isolated benchmark for the `mesh` message: only its own decoder is compiled.
// Run with `--no-default-features --features iso,mesh` (or `task bench-iso -- mesh`).
// Guard: isolation is lost if another message (or reflect/lazy) is compiled in,
// which happens if you forget --no-default-features (the default set enables all).
#[cfg(any(
feature = "api_response",
feature = "log_record",
feature = "analytics_event",
feature = "media_frame",
feature = "packed_tile",
feature = "google_message1",
feature = "reflect",
feature = "lazy"
))]
compile_error!("isolated `mesh` bench requires --no-default-features: another message/reflect/lazy feature is enabled, which defeats per-message isolation");
include!("common.rs");
use bench_buffa::bench::{__buffa::view::TriMeshView, TriMesh};

fn run(c: &mut Criterion) {
let data = include_bytes!("../../datasets/mesh.pb");
benchmark_decode::<TriMesh>(c, "buffa/mesh", data);
benchmark_json::<TriMesh>(c, "buffa/mesh", data);
let ds = load_dataset(data);
let bytes = total_payload_bytes(&ds);
let mut g = c.benchmark_group("buffa/mesh");
g.throughput(Throughput::Bytes(bytes));
g.bench_function("decode_view", |b| {
b.iter(|| {
for p in &ds.payload {
criterion::black_box(TriMeshView::decode_view(p).unwrap());
}
})
});
g.finish();
}
criterion::criterion_group!(grp, run);
criterion::criterion_main!(grp);
1 change: 1 addition & 0 deletions benchmarks/buffa/benches/packed_tile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
feature = "analytics_event",
feature = "media_frame",
feature = "google_message1",
feature = "mesh",
feature = "reflect",
feature = "lazy"
))]
Expand Down
26 changes: 25 additions & 1 deletion benchmarks/buffa/benches/protobuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use bench_buffa::bench::__buffa::lazy_view::{
};
use bench_buffa::bench::__buffa::view::{
analytics_event::PropertyView, AnalyticsEventView, ApiResponseView, LogRecordView,
MediaFrameView, PackedSignedView, PackedTileView,
MediaFrameView, PackedSignedView, PackedTileView, TriMeshView,
};
use bench_buffa::bench::__buffa::{oneof, view::oneof as view_oneof};
use bench_buffa::bench::*;
Expand Down Expand Up @@ -664,6 +664,28 @@ fn bench_packed_tile(c: &mut Criterion) {
);
}

// Many small singular sub-messages per repeated element: the regression target
// for `PointerRepr::Inline` (issue #248). The owned decode/merge cost is
// dominated by per-submessage allocation under the `Box` default.
fn bench_mesh(c: &mut Criterion) {
benchmark_decode::<TriMesh>(c, "buffa/mesh", include_bytes!("../../datasets/mesh.pb"));
}

fn bench_mesh_view(c: &mut Criterion) {
let dataset = load_dataset(include_bytes!("../../datasets/mesh.pb"));
let bytes = total_payload_bytes(&dataset);
let mut group = c.benchmark_group("buffa/mesh");
group.throughput(Throughput::Bytes(bytes));
group.bench_function("decode_view", |b| {
b.iter(|| {
for payload in &dataset.payload {
criterion::black_box(TriMeshView::decode_view(payload).unwrap());
}
});
});
group.finish();
}

// Control for the packed-varint reservation: every element is a negative
// (10-byte) varint, the worst case for the old byte-length reserve.
fn bench_packed_signed(c: &mut Criterion) {
Expand Down Expand Up @@ -740,6 +762,7 @@ criterion_group!(
bench_google_message1,
bench_media_frame,
bench_packed_tile,
bench_mesh,
bench_packed_signed,
);

Expand All @@ -751,6 +774,7 @@ criterion_group!(
bench_google_message1_view,
bench_media_frame_view,
bench_packed_tile_view,
bench_mesh_view,
bench_packed_signed_view,
bench_api_response_view_encode,
bench_log_record_view_encode,
Expand Down
1 change: 1 addition & 0 deletions benchmarks/buffa/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ fn main() {
("ANALYTICS_EVENT", "../proto/iso/analytics_event.proto"),
("MEDIA_FRAME", "../proto/iso/media_frame.proto"),
("PACKED_TILE", "../proto/iso/packed_tile.proto"),
("MESH", "../proto/iso/mesh.proto"),
(
"GOOGLE_MESSAGE1",
"../proto/benchmark_message1_proto3.proto",
Expand Down
3 changes: 2 additions & 1 deletion benchmarks/buffa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
feature = "log_record",
feature = "analytics_event",
feature = "media_frame",
feature = "packed_tile"
feature = "packed_tile",
feature = "mesh"
))]
#[allow(
clippy::derivable_impls,
Expand Down
Binary file added benchmarks/datasets/mesh.pb
Binary file not shown.
85 changes: 17 additions & 68 deletions benchmarks/gen-datasets/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading