Skip to content

Commit c58621b

Browse files
Improve ByteSize calculation for fields with fixed wire size.
Use popcount with a mask on the has bits to count the number of present fields instead of checking fields one by one with a conditional. PiperOrigin-RevId: 716692546
1 parent 3423d7c commit c58621b

File tree

4 files changed

+125
-175
lines changed

4 files changed

+125
-175
lines changed

src/google/protobuf/compiler/cpp/message.cc

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
#include <iterator>
2020
#include <limits>
2121
#include <memory>
22+
#include <optional>
2223
#include <string>
2324
#include <utility>
2425
#include <vector>
2526

27+
#include "absl/algorithm/container.h"
2628
#include "absl/container/flat_hash_map.h"
2729
#include "absl/container/flat_hash_set.h"
2830
#include "absl/functional/any_invocable.h"
@@ -5010,6 +5012,34 @@ std::vector<uint32_t> MessageGenerator::RequiredFieldsBitMask() const {
50105012
return masks;
50115013
}
50125014

5015+
static std::optional<int> FixedSize(const FieldDescriptor* field) {
5016+
if (field->is_repeated() || field->real_containing_oneof() ||
5017+
!field->has_presence()) {
5018+
return std::nullopt;
5019+
}
5020+
5021+
const size_t tag_size = WireFormat::TagSize(field->number(), field->type());
5022+
5023+
switch (field->type()) {
5024+
case FieldDescriptor::TYPE_FIXED32:
5025+
return tag_size + WireFormatLite::kFixed32Size;
5026+
case FieldDescriptor::TYPE_FIXED64:
5027+
return tag_size + WireFormatLite::kFixed64Size;
5028+
case FieldDescriptor::TYPE_SFIXED32:
5029+
return tag_size + WireFormatLite::kSFixed32Size;
5030+
case FieldDescriptor::TYPE_SFIXED64:
5031+
return tag_size + WireFormatLite::kSFixed64Size;
5032+
case FieldDescriptor::TYPE_FLOAT:
5033+
return tag_size + WireFormatLite::kFloatSize;
5034+
case FieldDescriptor::TYPE_DOUBLE:
5035+
return tag_size + WireFormatLite::kDoubleSize;
5036+
case FieldDescriptor::TYPE_BOOL:
5037+
return tag_size + WireFormatLite::kBoolSize;
5038+
default:
5039+
return std::nullopt;
5040+
}
5041+
}
5042+
50135043
void MessageGenerator::GenerateByteSize(io::Printer* p) {
50145044
if (HasSimpleBaseClass(descriptor_, options_)) return;
50155045

@@ -5039,14 +5069,45 @@ void MessageGenerator::GenerateByteSize(io::Printer* p) {
50395069
return;
50405070
}
50415071

5042-
std::vector<FieldChunk> chunks = CollectFields(
5043-
optimized_order_, options_,
5044-
[&](const FieldDescriptor* a, const FieldDescriptor* b) -> bool {
5072+
std::vector<const FieldDescriptor*> fixed;
5073+
std::vector<const FieldDescriptor*> rest;
5074+
for (auto* f : optimized_order_) {
5075+
if (FixedSize(f).has_value()) {
5076+
fixed.push_back(f);
5077+
} else {
5078+
rest.push_back(f);
5079+
}
5080+
}
5081+
5082+
// Sort the fixed fields to ensure maximum grouping.
5083+
// The layout of the fields is irrelevant because we are not going to read
5084+
// them. We only look at the hasbits.
5085+
const auto fixed_tuple = [&](auto* f) {
5086+
return std::make_tuple(HasWordIndex(f), FixedSize(f));
5087+
};
5088+
absl::c_sort(
5089+
fixed, [&](auto* a, auto* b) { return fixed_tuple(a) < fixed_tuple(b); });
5090+
std::vector<FieldChunk> fixed_chunks =
5091+
CollectFields(fixed, options_, [&](const auto* a, const auto* b) {
5092+
return fixed_tuple(a) == fixed_tuple(b);
5093+
});
5094+
5095+
std::vector<FieldChunk> chunks =
5096+
CollectFields(rest, options_, [&](const auto* a, const auto* b) {
50455097
return a->label() == b->label() && HasByteIndex(a) == HasByteIndex(b) &&
50465098
IsLikelyPresent(a, options_) == IsLikelyPresent(b, options_) &&
50475099
ShouldSplit(a, options_) == ShouldSplit(b, options_);
50485100
});
50495101

5102+
// Interleave the fixed chunks in the right place to be able to reuse
5103+
// cached_has_bits if available. Otherwise, add them to the end.
5104+
for (auto& chunk : fixed_chunks) {
5105+
auto it = std::find_if(chunks.begin(), chunks.end(), [&](auto& c) {
5106+
return HasWordIndex(c.fields[0]) == HasWordIndex(chunk.fields[0]);
5107+
});
5108+
chunks.insert(it, std::move(chunk));
5109+
}
5110+
50505111
p->Emit(
50515112
{{"handle_extension_set",
50525113
[&] {
@@ -5086,6 +5147,15 @@ void MessageGenerator::GenerateByteSize(io::Printer* p) {
50865147
auto it = chunks.begin();
50875148
auto end = chunks.end();
50885149
int cached_has_word_index = -1;
5150+
const auto update_cached_has_bits = [&](auto& fields) {
5151+
if (cached_has_word_index == HasWordIndex(fields.front())) return;
5152+
5153+
cached_has_word_index = HasWordIndex(fields.front());
5154+
p->Emit({{"index", cached_has_word_index}},
5155+
R"cc(
5156+
cached_has_bits = this_.$has_bits$[$index$];
5157+
)cc");
5158+
};
50895159

50905160
while (it != end) {
50915161
auto next =
@@ -5096,6 +5166,25 @@ void MessageGenerator::GenerateByteSize(io::Printer* p) {
50965166

50975167
while (it != next) {
50985168
const auto& fields = it->fields;
5169+
5170+
// If the chunk is a fixed size singular chunk, use a branchless
5171+
// approach for it.
5172+
if (std::optional<int> fsize = FixedSize(fields[0])) {
5173+
update_cached_has_bits(fields);
5174+
uint32_t mask = GenChunkMask(fields, has_bit_indices_);
5175+
p->Emit({{"mask", absl::StrFormat("0x%08xu", mask)},
5176+
{"popcount", absl::has_single_bit(mask)
5177+
? "static_cast<bool>"
5178+
: "::absl::popcount"},
5179+
{"fsize", *fsize}},
5180+
R"cc(
5181+
//~
5182+
total_size += $popcount$($mask$ & cached_has_bits) * $fsize$;
5183+
)cc");
5184+
++it;
5185+
continue;
5186+
}
5187+
50995188
const bool check_has_byte =
51005189
fields.size() > 1 && HasWordIndex(fields[0]) != kNoHasbit &&
51015190
!IsLikelyPresent(fields.back(), options_);
@@ -5112,14 +5201,7 @@ void MessageGenerator::GenerateByteSize(io::Printer* p) {
51125201
{"may_update_cached_has_word_index",
51135202
[&] {
51145203
if (!check_has_byte) return;
5115-
if (cached_has_word_index == HasWordIndex(fields.front()))
5116-
return;
5117-
5118-
cached_has_word_index = HasWordIndex(fields.front());
5119-
p->Emit({{"index", cached_has_word_index}},
5120-
R"cc(
5121-
cached_has_bits = this_.$has_bits$[$index$];
5122-
)cc");
5204+
update_cached_has_bits(fields);
51235205
}},
51245206
{"check_if_chunk_present",
51255207
[&] {

src/google/protobuf/compiler/java/java_features.pb.cc

Lines changed: 2 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/google/protobuf/cpp_features.pb.cc

Lines changed: 2 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)