pw_protobuf: More protobuf size reports
Further extends the pw_protobuf size reports to include decoders and
codegen size reports.
Change-Id: Id9b00e8b488520bc7e8d2cf9b8431dee083d63c2
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/110115
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Reviewed-by: Keir Mierle <keir@google.com>
Pigweed-Auto-Submit: Armando Montanez <amontanez@google.com>
diff --git a/pw_protobuf/BUILD.bazel b/pw_protobuf/BUILD.bazel
index b4dbf07..7501c68 100644
--- a/pw_protobuf/BUILD.bazel
+++ b/pw_protobuf/BUILD.bazel
@@ -209,6 +209,7 @@
"pw_protobuf_test_protos/optional.proto",
"pw_protobuf_test_protos/proto2.proto",
"pw_protobuf_test_protos/repeated.proto",
+ "pw_protobuf_test_protos/size_report.proto",
],
strip_import_prefix = "//pw_protobuf",
deps = [
diff --git a/pw_protobuf/BUILD.gn b/pw_protobuf/BUILD.gn
index b307c1d..bad3bd6 100644
--- a/pw_protobuf/BUILD.gn
+++ b/pw_protobuf/BUILD.gn
@@ -94,11 +94,20 @@
}
pw_doc_group("docs") {
- sources = [ "docs.rst" ]
+ sources = [
+ "docs.rst",
+ "size_report.rst",
+ ]
+ inputs = [
+ "pw_protobuf_test_protos/size_report.options",
+ "pw_protobuf_test_protos/size_report.proto",
+ ]
report_deps = [
"size_report:decoder_incremental",
"size_report:decoder_partial",
+ "size_report:oneof_codegen_size_comparison",
"size_report:protobuf_overview",
+ "size_report:simple_codegen_size_comparison",
]
}
@@ -216,6 +225,7 @@
"pw_protobuf_test_protos/optional.proto",
"pw_protobuf_test_protos/proto2.proto",
"pw_protobuf_test_protos/repeated.proto",
+ "pw_protobuf_test_protos/size_report.proto",
]
inputs = [
"pw_protobuf_test_protos/full_test.options",
diff --git a/pw_protobuf/docs.rst b/pw_protobuf/docs.rst
index 0c192ae..c910772 100644
--- a/pw_protobuf/docs.rst
+++ b/pw_protobuf/docs.rst
@@ -54,6 +54,11 @@
Customer.name max_size:32
+.. toctree::
+ :maxdepth: 1
+
+ size_report
+
Message Structures
==================
The highest level API is based around message structures created through C++
diff --git a/pw_protobuf/pw_protobuf_test_protos/size_report.options b/pw_protobuf/pw_protobuf_test_protos/size_report.options
new file mode 100644
index 0000000..2b63e69
--- /dev/null
+++ b/pw_protobuf/pw_protobuf_test_protos/size_report.options
@@ -0,0 +1,15 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+pw.protobuf_size_report.Reponse.responses max_count:4
diff --git a/pw_protobuf/pw_protobuf_test_protos/size_report.proto b/pw_protobuf/pw_protobuf_test_protos/size_report.proto
new file mode 100644
index 0000000..55783f2
--- /dev/null
+++ b/pw_protobuf/pw_protobuf_test_protos/size_report.proto
@@ -0,0 +1,57 @@
+// Copyright 2019 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+syntax = "proto3";
+
+package pw.protobuf_size_report;
+
+message ItemInfo {
+ enum Access {
+ NONE = 0;
+ READ = 1;
+ WRITE = 2;
+ READ_AND_WRITE = 3;
+ }
+ uint64 offset = 1;
+ uint32 size = 2;
+ Access access_level = 3;
+}
+
+message ResponseInfo {
+ oneof key {
+ string key_string = 1;
+ fixed32 key_token = 2;
+ }
+
+ optional int64 timestamp = 3;
+ optional bool has_value = 4;
+ ItemInfo item_info = 5;
+}
+
+message Response {
+ repeated ResponseInfo responses = 1;
+}
+
+message LookupRequest {
+ message AuthInfo {
+ optional uint32 id = 1;
+ optional uint32 token = 2;
+ }
+ oneof key {
+ string key_string = 1;
+ fixed32 key_token = 2;
+ }
+ optional uint32 items_per_response = 3;
+ AuthInfo auth_info = 4;
+ bool add_timestamp = 5;
+}
diff --git a/pw_protobuf/size_report.rst b/pw_protobuf/size_report.rst
new file mode 100644
index 0000000..1adf6d2
--- /dev/null
+++ b/pw_protobuf/size_report.rst
@@ -0,0 +1,62 @@
+.. _module-pw_protobuf-size_report:
+
+================================
+pw_protobuf extended size report
+================================
+pw_protobuf can impact binary size very differently depending on how it's used.
+A series of examples are provided below to illustrate how much certain use cases
+affect binary size.
+
+--------
+Overview
+--------
+This module includes a proto encoder, two different proto decoders (one that
+operates on a ``pw::stream::StreamReader`` and another that operates on an in-
+memory buffer), codegen for direct wire-format encoders/decoders, and a
+table-based codegen system for constructing proto messages as in-memory structs.
+
+Here's a brief overview of the different encoder/decoder costs:
+
+.. include:: size_report/protobuf_overview
+
+.. note::
+
+ There's some overhead involved in ensuring all of the encoder/decoder
+ functionality is pulled in. Check the per-symbol breakdown for more details.
+
+--------------------------------
+Encoder/decoder codegen overhead
+--------------------------------
+The different proto serialization/deserialization codegen methods have different
+overhead. Some have a higher up-front cost, but lower complexity (and therefore
+smaller compiler generated code) at the sites of usage. Others trade lower
+up-front code size cost for more complexity at the proto construction and read
+sites.
+
+This example uses the following proto message to construct a variety of use
+cases to illustrate how code and memory requirements can change depending on
+the complexity of the proto message being encoded/decoded.
+
+.. literalinclude:: pw_protobuf_test_protos/size_report.proto
+ :language: protobuf
+ :lines: 14-
+
+This proto is configured with the following options file:
+
+.. literalinclude:: pw_protobuf_test_protos/size_report.options
+ :lines: 14-
+
+Trivial proto
+=============
+This is a size report for encoding/decoding the ``pw.protobuf.test.ItemInfo``
+message. This is a pretty trivial message with just a few integers.
+
+.. include:: size_report/simple_codegen_size_comparison
+
+Optional and oneof
+==================
+This is a size report for encoding/decoding the
+``pw.protobuf.test.ResponseInfo`` message. This is slightly more complex message
+that has a few explicitly optional fields, a oneof, and a submessage.
+
+.. include:: size_report/oneof_codegen_size_comparison
diff --git a/pw_protobuf/size_report/BUILD.bazel b/pw_protobuf/size_report/BUILD.bazel
index aea516d..3e4d7c0 100644
--- a/pw_protobuf/size_report/BUILD.bazel
+++ b/pw_protobuf/size_report/BUILD.bazel
@@ -62,6 +62,17 @@
)
pw_cc_binary(
+ name = "proto_base",
+ srcs = [
+ "proto_base.cc",
+ ],
+ deps = [
+ ":proto_bloat",
+ "//pw_bloat:bloat_this_binary",
+ ],
+)
+
+pw_cc_binary(
name = "encode_decode_core",
srcs = [
"encode_decode_core.cc",
@@ -84,12 +95,67 @@
)
pw_cc_binary(
- name = "proto_base",
- srcs = [
- "proto_base.cc",
- ],
+ name = "messages_no_codegen",
+ srcs = ["simple_codegen_comparison.cc"],
+ defines = ["_PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN=1"],
deps = [
":proto_bloat",
"//pw_bloat:bloat_this_binary",
+ "//pw_protobuf:codegen_test_proto_cc.pwpb",
+ ],
+)
+
+pw_cc_binary(
+ name = "messages_wire_format",
+ srcs = ["simple_codegen_comparison.cc"],
+ defines = ["_PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT=1"],
+ deps = [
+ ":proto_bloat",
+ "//pw_bloat:bloat_this_binary",
+ "//pw_protobuf:codegen_test_proto_cc.pwpb",
+ ],
+)
+
+pw_cc_binary(
+ name = "messages_message",
+ srcs = ["simple_codegen_comparison.cc"],
+ defines = ["_PW_PROTOBUF_SIZE_REPORT_MESSAGE=1"],
+ deps = [
+ ":proto_bloat",
+ "//pw_bloat:bloat_this_binary",
+ "//pw_protobuf:codegen_test_proto_cc.pwpb",
+ ],
+)
+
+pw_cc_binary(
+ name = "oneof_no_codegen",
+ srcs = ["oneof_codegen_comparison.cc"],
+ defines = ["_PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN=1"],
+ deps = [
+ ":proto_bloat",
+ "//pw_bloat:bloat_this_binary",
+ "//pw_protobuf:codegen_test_proto_cc.pwpb",
+ ],
+)
+
+pw_cc_binary(
+ name = "oneof_wire_format",
+ srcs = ["oneof_codegen_comparison.cc"],
+ defines = ["_PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT=1"],
+ deps = [
+ ":proto_bloat",
+ "//pw_bloat:bloat_this_binary",
+ "//pw_protobuf:codegen_test_proto_cc.pwpb",
+ ],
+)
+
+pw_cc_binary(
+ name = "oneof_message",
+ srcs = ["oneof_codegen_comparison.cc"],
+ defines = ["_PW_PROTOBUF_SIZE_REPORT_MESSAGE=1"],
+ deps = [
+ ":proto_bloat",
+ "//pw_bloat:bloat_this_binary",
+ "//pw_protobuf:codegen_test_proto_cc.pwpb",
],
)
diff --git a/pw_protobuf/size_report/BUILD.gn b/pw_protobuf/size_report/BUILD.gn
index dd3d310..a28f2da 100644
--- a/pw_protobuf/size_report/BUILD.gn
+++ b/pw_protobuf/size_report/BUILD.gn
@@ -62,6 +62,66 @@
sources = [ "message_core.cc" ]
}
+pw_executable("messages_no_codegen") {
+ defines = [ "_PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN=1" ]
+ deps = [
+ ":proto_bloat",
+ "$dir_pw_bloat:bloat_this_binary",
+ "..:codegen_test_protos.pwpb",
+ ]
+ sources = [ "simple_codegen_comparison.cc" ]
+}
+
+pw_executable("messages_wire_format") {
+ defines = [ "_PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT=1" ]
+ deps = [
+ ":proto_bloat",
+ "$dir_pw_bloat:bloat_this_binary",
+ "..:codegen_test_protos.pwpb",
+ ]
+ sources = [ "simple_codegen_comparison.cc" ]
+}
+
+pw_executable("messages_message") {
+ defines = [ "_PW_PROTOBUF_SIZE_REPORT_MESSAGE=1" ]
+ deps = [
+ ":proto_bloat",
+ "$dir_pw_bloat:bloat_this_binary",
+ "..:codegen_test_protos.pwpb",
+ ]
+ sources = [ "simple_codegen_comparison.cc" ]
+}
+
+pw_executable("oneof_no_codegen") {
+ defines = [ "_PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN=1" ]
+ deps = [
+ ":proto_bloat",
+ "$dir_pw_bloat:bloat_this_binary",
+ "..:codegen_test_protos.pwpb",
+ ]
+ sources = [ "oneof_codegen_comparison.cc" ]
+}
+
+pw_executable("oneof_wire_format") {
+ defines = [ "_PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT=1" ]
+ deps = [
+ ":proto_bloat",
+ "$dir_pw_bloat:bloat_this_binary",
+ "..:codegen_test_protos.pwpb",
+ ]
+ sources = [ "oneof_codegen_comparison.cc" ]
+}
+
+pw_executable("oneof_message") {
+ defines = [ "_PW_PROTOBUF_SIZE_REPORT_MESSAGE=1" ]
+ deps = [
+ ":proto_bloat",
+ "$dir_pw_bloat:bloat_this_binary",
+ "..:codegen_test_protos.pwpb",
+ ]
+ sources = [ "oneof_codegen_comparison.cc" ]
+}
+
pw_toolchain_size_diff("decoder_partial") {
base_executable = pw_bloat_empty_base
diff_executable = _decoder_partial
@@ -81,6 +141,66 @@
title = "Adding more fields to decode callback"
}
+pw_size_diff("simple_codegen_size_comparison") {
+ title = "Pigweed protobuf codegen size report"
+ source_filter = "pw::protobuf_size_report::*"
+ binaries = [
+ {
+ target = ":messages_no_codegen"
+ base = ":message_core"
+ label = "Direct wire-format proto encoder"
+ },
+ {
+ target = ":messages_wire_format"
+ base = ":message_core"
+ label = "Generated wrapped wire-format encoder"
+ },
+ {
+ target = ":messages_message"
+ base = ":message_core"
+ label = "Generated message encoder"
+ },
+ ]
+}
+
+pw_size_diff("oneof_codegen_size_comparison") {
+ title = "Pigweed protobuf codegen size report"
+ source_filter = "pw::protobuf_size_report::*"
+ binaries = [
+ {
+ target = ":oneof_no_codegen"
+ base = ":message_core"
+ label = "Direct wire-format proto encoder"
+ },
+ {
+ target = ":oneof_wire_format"
+ base = ":message_core"
+ label = "Generated wrapped wire-format encoder"
+ },
+ {
+ target = ":oneof_message"
+ base = ":message_core"
+ label = "Generated message encoder"
+ },
+ ]
+}
+
+pw_size_diff("message_size_report") {
+ title = "Pigweed protobuf message size report"
+ binaries = [
+ {
+ target = ":one_message_struct_write_vs_base"
+ base = ":proto_base"
+ label = "Message encoder flash cost (incl. wire-format encoder)"
+ },
+ {
+ target = ":one_message_struct_write_vs_encoder"
+ base = ":encoder_full"
+ label = "Message encoder flash cost (excl. wire-format encoder)"
+ },
+ ]
+}
+
pw_size_diff("protobuf_overview") {
title = "Pigweed protobuf encoder size report"
source_filter = "pw::protobuf::*|section .code"
diff --git a/pw_protobuf/size_report/oneof_codegen_comparison.cc b/pw_protobuf/size_report/oneof_codegen_comparison.cc
new file mode 100644
index 0000000..8c06b45
--- /dev/null
+++ b/pw_protobuf/size_report/oneof_codegen_comparison.cc
@@ -0,0 +1,387 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "proto_bloat.h"
+#include "pw_bloat/bloat_this_binary.h"
+#include "pw_protobuf/decoder.h"
+#include "pw_protobuf/encoder.h"
+#include "pw_protobuf/stream_decoder.h"
+#include "pw_protobuf_test_protos/size_report.pwpb.h"
+#include "pw_result/result.h"
+#include "pw_status/status.h"
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+#define _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN 0
+#endif // _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+#define _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT 0
+#endif // _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+#define _PW_PROTOBUF_SIZE_REPORT_MESSAGE 0
+#endif // _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+namespace pw::protobuf_size_report {
+namespace {
+
+template <typename T>
+PW_NO_INLINE void ConsumeValue(T val) {
+ [[maybe_unused]] volatile T no_optimize = val;
+}
+
+#if _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> encode_buffer;
+pw::protobuf::MemoryEncoder encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+ pw::Status status;
+ volatile enum KeyType : uint32_t {
+ NONE = 0,
+ KEY_STRING = 1,
+ KEY_TOKEN = 2,
+ } which_key = KeyType::KEY_STRING;
+ volatile bool has_timestamp = true;
+ volatile bool has_has_value = false;
+ if (which_key == KeyType::KEY_STRING) {
+ encoder.WriteString(1, "test");
+ } else if (which_key == KeyType::KEY_TOKEN) {
+ encoder.WriteFixed32(2, 99999);
+ }
+
+ if (has_timestamp) {
+ encoder.WriteInt64(3, 1663003467);
+ }
+
+ if (has_has_value) {
+ encoder.WriteBool(4, true);
+ }
+
+ {
+ pw::protobuf::StreamEncoder submessage_encoder =
+ encoder.GetNestedEncoder(5);
+ status.Update(submessage_encoder.WriteInt64(1, 0x5001DBADFEEDBEE5));
+ status.Update(submessage_encoder.WriteInt32(2, 128));
+ status.Update(submessage_encoder.WriteInt32(3, 2));
+ }
+ ConsumeValue(status);
+}
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::protobuf::Decoder decoder(decode_buffer);
+
+PW_NO_INLINE void DecodeItemInfo(pw::ConstByteSpan data) {
+ pw::protobuf::Decoder submessage_decoder(data);
+ while (submessage_decoder.Next().ok()) {
+ switch (submessage_decoder.FieldNumber()) {
+ case static_cast<uint32_t>(ItemInfo::Fields::OFFSET): {
+ uint64_t value;
+ if (submessage_decoder.ReadUint64(&value).ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case static_cast<uint32_t>(ItemInfo::Fields::SIZE): {
+ uint32_t value;
+ if (submessage_decoder.ReadUint32(&value).ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case static_cast<uint32_t>(ItemInfo::Fields::ACCESS_LEVEL): {
+ uint32_t value;
+
+ if (submessage_decoder.ReadUint32(&value).ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ }
+ }
+}
+
+PW_NO_INLINE void BasicDecode() {
+ volatile enum KeyType : uint32_t {
+ NONE = 0,
+ KEY_STRING = 1,
+ KEY_TOKEN = 2,
+ } which_key = KeyType::NONE;
+ volatile bool has_timestamp = false;
+ volatile bool has_has_value = false;
+
+ while (decoder.Next().ok()) {
+ switch (decoder.FieldNumber()) {
+ case static_cast<uint32_t>(ResponseInfo::Fields::KEY_STRING): {
+ which_key = KeyType::KEY_STRING;
+ std::string_view value;
+ if (decoder.ReadString(&value).ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case static_cast<uint32_t>(ResponseInfo::Fields::KEY_TOKEN): {
+ which_key = KeyType::KEY_TOKEN;
+ uint32_t value;
+ if (decoder.ReadUint32(&value).ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case static_cast<uint32_t>(ResponseInfo::Fields::TIMESTAMP): {
+ uint64_t value;
+ has_timestamp = true;
+ if (decoder.ReadUint64(&value).ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case static_cast<uint32_t>(ResponseInfo::Fields::HAS_VALUE): {
+ bool value;
+ has_has_value = true;
+ if (decoder.ReadBool(&value).ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case static_cast<uint32_t>(ResponseInfo::Fields::ITEM_INFO): {
+ pw::ConstByteSpan value;
+ if (decoder.ReadBytes(&value).ok()) {
+ DecodeItemInfo(value);
+ }
+ break;
+ }
+ }
+ }
+ ConsumeValue(which_key);
+ ConsumeValue(has_timestamp);
+ ConsumeValue(has_has_value);
+}
+
+#endif // _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+#if _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> encode_buffer;
+ResponseInfo::MemoryEncoder encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+ pw::Status status;
+ volatile enum KeyType : uint32_t {
+ NONE = 0,
+ KEY_STRING = 1,
+ KEY_TOKEN = 2,
+ } which_key = KeyType::KEY_STRING;
+ volatile bool has_timestamp = true;
+ volatile bool has_has_value = false;
+ if (which_key == KeyType::KEY_STRING) {
+ encoder.WriteKeyString("test");
+ } else if (which_key == KeyType::KEY_TOKEN) {
+ encoder.WriteKeyToken(99999);
+ }
+
+ if (has_timestamp) {
+ encoder.WriteTimestamp(1663003467);
+ }
+
+ if (has_has_value) {
+ encoder.WriteHasValue(true);
+ }
+
+ {
+ ItemInfo::StreamEncoder submessage_encoder = encoder.GetItemInfoEncoder();
+ status.Update(submessage_encoder.WriteOffset(0x5001DBADFEEDBEE5));
+ status.Update(submessage_encoder.WriteSize(128));
+ status.Update(submessage_encoder.WriteAccessLevel(ItemInfo::Access::WRITE));
+ }
+ ConsumeValue(status);
+}
+
+std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::stream::MemoryReader reader(decode_buffer);
+ResponseInfo::StreamDecoder decoder(reader);
+
+PW_NO_INLINE void DecodeItemInfo(ItemInfo::StreamDecoder& submessage_decoder) {
+ while (submessage_decoder.Next().ok()) {
+ pw::Result<ItemInfo::Fields> field = submessage_decoder.Field();
+ if (!field.ok()) {
+ ConsumeValue(field.status());
+ return;
+ }
+
+ switch (field.value()) {
+ case ItemInfo::Fields::OFFSET: {
+ pw::Result<uint64_t> value = submessage_decoder.ReadOffset();
+ if (value.ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case ItemInfo::Fields::SIZE: {
+ pw::Result<uint32_t> value = submessage_decoder.ReadSize();
+ if (value.ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case ItemInfo::Fields::ACCESS_LEVEL: {
+ pw::Result<ItemInfo::Access> value =
+ submessage_decoder.ReadAccessLevel();
+ if (value.ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ }
+ }
+}
+
+PW_NO_INLINE void BasicDecode() {
+ volatile enum KeyType : uint32_t {
+ NONE = 0,
+ KEY_STRING = 1,
+ KEY_TOKEN = 2,
+ } which_key = KeyType::NONE;
+ volatile bool has_timestamp = false;
+ volatile bool has_has_value = false;
+
+ while (decoder.Next().ok()) {
+ while (decoder.Next().ok()) {
+ pw::Result<ResponseInfo::Fields> field = decoder.Field();
+ if (!field.ok()) {
+ ConsumeValue(field.status());
+ return;
+ }
+
+ switch (field.value()) {
+ case ResponseInfo::Fields::KEY_STRING: {
+ which_key = KeyType::KEY_STRING;
+ std::array<char, 8> value;
+ pw::StatusWithSize status = decoder.ReadKeyString(value);
+ if (status.ok()) {
+ ConsumeValue(pw::span(value));
+ }
+ break;
+ }
+ case ResponseInfo::Fields::KEY_TOKEN: {
+ which_key = KeyType::KEY_TOKEN;
+ pw::Result<uint32_t> value = decoder.ReadKeyToken();
+ if (value.ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case ResponseInfo::Fields::TIMESTAMP: {
+ has_timestamp = true;
+ pw::Result<int64_t> value = decoder.ReadTimestamp();
+ if (value.ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case ResponseInfo::Fields::HAS_VALUE: {
+ has_has_value = true;
+ pw::Result<bool> value = decoder.ReadHasValue();
+ if (value.ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case ResponseInfo::Fields::ITEM_INFO: {
+ ItemInfo::StreamDecoder submessage_decoder =
+ decoder.GetItemInfoDecoder();
+ DecodeItemInfo(submessage_decoder);
+ break;
+ }
+ }
+ }
+ }
+ ConsumeValue(which_key);
+ ConsumeValue(has_timestamp);
+ ConsumeValue(has_has_value);
+}
+#endif // _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+#if _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+ResponseInfo::Message message;
+
+std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> encode_buffer;
+ResponseInfo::MemoryEncoder encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+ volatile enum KeyType : uint32_t {
+ NONE = 0,
+ KEY_STRING = 1,
+ KEY_TOKEN = 2,
+ } which_key = KeyType::KEY_STRING;
+ volatile bool has_timestamp = true;
+ volatile bool has_has_value = false;
+ if (which_key == KeyType::KEY_STRING) {
+ message.key_string.SetEncoder(
+ [](ResponseInfo::StreamEncoder& key_string_encoder) -> pw::Status {
+ key_string_encoder.WriteKeyString("test");
+ return pw::OkStatus();
+ });
+ } else if (which_key == KeyType::KEY_TOKEN) {
+ message.key_token = 99999;
+ }
+ message.timestamp =
+ has_timestamp ? std::optional<uint32_t>(1663003467) : std::nullopt;
+ message.has_value = has_has_value ? std::optional<bool>(false) : std::nullopt;
+
+ message.item_info.offset = 0x5001DBADFEEDBEE5;
+ message.item_info.size = 128;
+ message.item_info.access_level = ItemInfo::Access::WRITE;
+ ConsumeValue(encoder.Write(message));
+}
+
+std::array<std::byte, ResponseInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::stream::MemoryReader reader(decode_buffer);
+ResponseInfo::StreamDecoder decoder(reader);
+
+PW_NO_INLINE void BasicDecode() {
+ volatile enum KeyType : uint32_t {
+ NONE = 0,
+ KEY_STRING = 1,
+ KEY_TOKEN = 2,
+ } which_key = KeyType::NONE;
+ volatile bool has_timestamp = false;
+ volatile bool has_has_value = false;
+ if (pw::Status status = decoder.Read(message); status.ok()) {
+ ConsumeValue(status);
+ has_timestamp = message.timestamp.has_value();
+ has_has_value = message.has_value.has_value();
+ }
+ ConsumeValue(which_key);
+ ConsumeValue(has_timestamp);
+ ConsumeValue(has_has_value);
+}
+#endif // _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+} // namespace
+} // namespace pw::protobuf_size_report
+
+int main() {
+ pw::bloat::BloatThisBinary();
+ pw::protobuf_size_report::BloatWithBase();
+ pw::protobuf_size_report::BloatWithEncoder();
+ pw::protobuf_size_report::BloatWithStreamDecoder();
+ pw::protobuf_size_report::BloatWithDecoder();
+ pw::protobuf_size_report::BloatWithTableEncoder();
+ pw::protobuf_size_report::BloatWithTableDecoder();
+ pw::protobuf_size_report::BasicEncode();
+ pw::protobuf_size_report::BasicDecode();
+ return 0;
+}
diff --git a/pw_protobuf/size_report/simple_codegen_comparison.cc b/pw_protobuf/size_report/simple_codegen_comparison.cc
new file mode 100644
index 0000000..277a8f1
--- /dev/null
+++ b/pw_protobuf/size_report/simple_codegen_comparison.cc
@@ -0,0 +1,181 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "proto_bloat.h"
+#include "pw_bloat/bloat_this_binary.h"
+#include "pw_protobuf/decoder.h"
+#include "pw_protobuf/encoder.h"
+#include "pw_protobuf/stream_decoder.h"
+#include "pw_protobuf_test_protos/size_report.pwpb.h"
+#include "pw_result/result.h"
+#include "pw_status/status.h"
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+#define _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN 0
+#endif // _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+#define _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT 0
+#endif // _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+#ifndef _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+#define _PW_PROTOBUF_SIZE_REPORT_MESSAGE 0
+#endif // _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+namespace pw::protobuf_size_report {
+namespace {
+
+template <typename T>
+PW_NO_INLINE void ConsumeValue(T val) {
+ [[maybe_unused]] volatile T no_optimize = val;
+}
+
+#if _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> encode_buffer;
+pw::protobuf::MemoryEncoder generic_encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+ pw::Status status;
+ status.Update(generic_encoder.WriteInt64(1, 0x5001DBADFEEDBEE5));
+ status.Update(generic_encoder.WriteInt32(2, 128));
+ status.Update(generic_encoder.WriteInt32(3, 2));
+ ConsumeValue(status);
+}
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::protobuf::Decoder generic_decoder(decode_buffer);
+
+PW_NO_INLINE void BasicDecode() {
+ while (generic_decoder.Next().ok()) {
+ switch (generic_decoder.FieldNumber()) {
+ case static_cast<uint32_t>(ItemInfo::Fields::OFFSET): {
+ uint64_t value;
+ if (generic_decoder.ReadUint64(&value).ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case static_cast<uint32_t>(ItemInfo::Fields::SIZE): {
+ uint32_t value;
+ if (generic_decoder.ReadUint32(&value).ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case static_cast<uint32_t>(ItemInfo::Fields::ACCESS_LEVEL): {
+ uint32_t value;
+
+ if (generic_decoder.ReadUint32(&value).ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ }
+ }
+}
+#endif // _PW_PROTOBUF_SIZE_REPORT_NO_CODEGEN
+
+#if _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> encode_buffer;
+ItemInfo::MemoryEncoder encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+ pw::Status status;
+ status.Update(encoder.WriteOffset(0x5001DBADFEEDBEE5));
+ status.Update(encoder.WriteSize(128));
+ status.Update(encoder.WriteAccessLevel(ItemInfo::Access::WRITE));
+ ConsumeValue(status);
+}
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::stream::MemoryReader reader(decode_buffer);
+ItemInfo::StreamDecoder decoder(reader);
+
+PW_NO_INLINE void BasicDecode() {
+ while (decoder.Next().ok()) {
+ pw::Result<ItemInfo::Fields> field = decoder.Field();
+ if (!field.ok()) {
+ ConsumeValue(field.status());
+ return;
+ }
+
+ switch (field.value()) {
+ case ItemInfo::Fields::OFFSET: {
+ pw::Result<uint64_t> value = decoder.ReadOffset();
+ if (value.ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case ItemInfo::Fields::SIZE: {
+ pw::Result<uint32_t> value = decoder.ReadSize();
+ if (value.ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ case ItemInfo::Fields::ACCESS_LEVEL: {
+ pw::Result<ItemInfo::Access> value = decoder.ReadAccessLevel();
+ if (value.ok()) {
+ ConsumeValue(value);
+ }
+ break;
+ }
+ }
+ }
+}
+#endif // _PW_PROTOBUF_SIZE_REPORT_WIRE_FORMAT
+
+#if _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+ItemInfo::Message message;
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> encode_buffer;
+ItemInfo::MemoryEncoder encoder(encode_buffer);
+
+PW_NO_INLINE void BasicEncode() {
+ message.offset = 0x5001DBADFEEDBEE5;
+ message.size = 128;
+ message.access_level = ItemInfo::Access::WRITE;
+ ConsumeValue(encoder.Write(message));
+}
+
+std::array<std::byte, ItemInfo::kMaxEncodedSizeBytes> decode_buffer;
+pw::stream::MemoryReader reader(decode_buffer);
+ItemInfo::StreamDecoder decoder(reader);
+
+PW_NO_INLINE void BasicDecode() {
+ if (pw::Status status = decoder.Read(message); status.ok()) {
+ ConsumeValue(status);
+ }
+}
+#endif // _PW_PROTOBUF_SIZE_REPORT_MESSAGE
+
+} // namespace
+} // namespace pw::protobuf_size_report
+
+int main() {
+ pw::bloat::BloatThisBinary();
+ pw::protobuf_size_report::BloatWithBase();
+ pw::protobuf_size_report::BloatWithEncoder();
+ pw::protobuf_size_report::BloatWithStreamDecoder();
+ pw::protobuf_size_report::BloatWithDecoder();
+ pw::protobuf_size_report::BloatWithTableEncoder();
+ pw::protobuf_size_report::BloatWithTableDecoder();
+ pw::protobuf_size_report::BasicEncode();
+ pw::protobuf_size_report::BasicDecode();
+ return 0;
+}