Skip to content

Commit b64e490

Browse files
Add IsEmpty() function to reflection.
This is faster than the current way of checking if a message is empty, which is to call ListFields() and check if the vector is empty. PiperOrigin-RevId: 808175207
1 parent 73a75ff commit b64e490

6 files changed

Lines changed: 230 additions & 27 deletions

File tree

src/google/protobuf/extension_set.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@ void ExtensionSet::DeleteFlatMap(const ExtensionSet::KeyValue* flat,
219219
// const DescriptorPool* pool,
220220
// vector<const FieldDescriptor*>* output) const
221221

222+
bool ExtensionSet::IsEmpty() const {
223+
if (IsCompletelyEmpty()) return true;
224+
return !AnyOfNoPrefetch(
225+
[](const int number, const Extension& ext) { return ext.IsSet(); });
226+
}
227+
222228
bool ExtensionSet::Has(int number) const {
223229
const Extension* ext = FindOrNull(number);
224230
if (ext == nullptr) return false;

src/google/protobuf/extension_set.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,11 @@ class PROTOBUF_EXPORT ExtensionSet {
301301
void AppendToList(const Descriptor* extendee, const DescriptorPool* pool,
302302
std::vector<const FieldDescriptor*>* output) const;
303303

304+
// Whether there are any fields which are currently present. Note that this
305+
// is different from IsCompletelyEmpty(), which returns false if the list has
306+
// any capacity; and Size(), which also accounts for cleared fields.
307+
bool IsEmpty() const;
308+
304309
// =================================================================
305310
// Accessors
306311
//
@@ -747,6 +752,7 @@ class PROTOBUF_EXPORT ExtensionSet {
747752
void Clear();
748753
int GetSize() const;
749754
void Free();
755+
bool IsSet() const { return is_repeated ? GetSize() > 0 : !is_cleared; }
750756
size_t SpaceUsedExcludingSelfLong() const;
751757
bool IsInitialized(const ExtensionSet* ext_set, const MessageLite* extendee,
752758
int number, Arena* arena) const;
@@ -1004,6 +1010,19 @@ class PROTOBUF_EXPORT ExtensionSet {
10041010
for (Iterator it = begin; it != end; ++it) func(it->first, it->second);
10051011
}
10061012

1013+
// Loops through [begin, end), and returns true as soon as some element
1014+
// satisfies predicate. Returns false if no element satisfies predicate.
1015+
template <typename Iterator, typename KeyValueFunctor>
1016+
static bool AnyOfNoPrefetch(Iterator begin, Iterator end,
1017+
KeyValueFunctor predicate) {
1018+
for (Iterator it = begin; it != end; ++it) {
1019+
if (predicate(it->first, it->second)) {
1020+
return true;
1021+
}
1022+
}
1023+
return false;
1024+
}
1025+
10071026
// Applies a functor to the <int, Extension&> pairs in sorted order.
10081027
template <typename KeyValueFunctor>
10091028
void ForEachNoPrefetch(KeyValueFunctor func) {
@@ -1026,6 +1045,18 @@ class PROTOBUF_EXPORT ExtensionSet {
10261045
ForEachNoPrefetch(flat_begin(), flat_end(), std::move(func));
10271046
}
10281047

1048+
// Loops through all <int, Extension&> pairs in sorted order, and returns true
1049+
// as soon as some element satisfies `predicate`. Returns false if no element
1050+
// satisfies predicate.
1051+
template <typename KeyValueFunctor>
1052+
bool AnyOfNoPrefetch(KeyValueFunctor predicate) const {
1053+
if (ABSL_PREDICT_FALSE(is_large())) {
1054+
return AnyOfNoPrefetch(map_.large->begin(), map_.large->end(),
1055+
std::move(predicate));
1056+
}
1057+
return AnyOfNoPrefetch(flat_begin(), flat_end(), std::move(predicate));
1058+
}
1059+
10291060
// Returns true if nothing is allocated in the ExtensionSet.
10301061
bool IsCompletelyEmpty() const {
10311062
return flat_size_ == 0 && flat_capacity_ == 0;

src/google/protobuf/extension_set_heavy.cc

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,7 @@ void ExtensionSet::AppendToList(
5555
std::vector<const FieldDescriptor*>* output) const {
5656
ForEach(
5757
[extendee, pool, &output](int number, const Extension& ext) {
58-
bool has = false;
59-
if (ext.is_repeated) {
60-
has = ext.GetSize() > 0;
61-
} else {
62-
has = !ext.is_cleared;
63-
}
64-
65-
if (has) {
58+
if (ext.IsSet()) {
6659
// TODO: Looking up each field by number is somewhat
6760
// unfortunate.
6861
// Is there a better way? The problem is that descriptors are

src/google/protobuf/generated_message_reflection.cc

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include <algorithm>
1515
#include <atomic>
16+
#include <cstddef>
1617
#include <cstdint>
1718
#include <cstring>
1819
#include <new> // IWYU pragma: keep for operator delete
@@ -1761,13 +1762,23 @@ bool CreateUnknownEnumValues(const FieldDescriptor* field) {
17611762
} // namespace internal
17621763
using internal::CreateUnknownEnumValues;
17631764

1764-
void Reflection::ListFields(const Message& message,
1765-
std::vector<const FieldDescriptor*>* output) const {
1766-
output->clear();
1767-
1768-
// Optimization: The default instance never has any fields set.
1769-
if (schema_.IsDefaultInstance(message)) return;
17701765

1766+
// Common functionality shared by IsEmpty and ListFields for iterating over
1767+
// all set fields.
1768+
//
1769+
// If kForIsEmpty=true, returns 0 if the message is empty, and 1 otherwise.
1770+
//
1771+
// If kForIsEmpty=false, populates output with all filled fields. Return value
1772+
// is the last field set, or 0 if the message is empty.
1773+
template <bool kForIsEmpty, typename MaybeFieldDescriptorVec>
1774+
inline int32_t Reflection::IsEmptyOrCollectSetFields(
1775+
const Message& message,
1776+
// Cached locally rather than a member variable for performance.
1777+
const Descriptor& descriptor,
1778+
[[maybe_unused]] MaybeFieldDescriptorVec output) const {
1779+
// Output should be null only if we are not computing the full output list.
1780+
static_assert(kForIsEmpty ==
1781+
std::is_same_v<MaybeFieldDescriptorVec, std::nullptr_t>);
17711782
// Optimization: Avoid calling GetHasBits() and HasOneofField() many times
17721783
// within the field loop. We allow this violation of ReflectionSchema
17731784
// encapsulation because this function takes a noticeable about of CPU
@@ -1776,17 +1787,33 @@ void Reflection::ListFields(const Message& message,
17761787
const uint32_t* const has_bits =
17771788
schema_.HasHasbits() ? GetHasBits(message) : nullptr;
17781789
const uint32_t* const has_bits_indices = schema_.has_bit_indices_;
1779-
const Descriptor* const descriptor = descriptor_;
1780-
output->reserve(descriptor->field_count());
1790+
if constexpr (!kForIsEmpty) {
1791+
output->reserve(descriptor.field_count());
1792+
}
17811793
// Fields in messages are usually added with the increasing tags.
17821794
uint32_t last = 0; // UINT32_MAX if out-of-order
1783-
auto append_to_output = [&last, &output](const FieldDescriptor* field) {
1784-
CheckInOrder(field, &last);
1785-
output->push_back(field);
1786-
};
1795+
// Stifle unused variable compilation error when kForIsEmpty is true.
1796+
[[maybe_unused]] const auto append_to_output =
1797+
[&last, output](const FieldDescriptor& field) {
1798+
if constexpr (!kForIsEmpty) {
1799+
CheckInOrder(&field, &last);
1800+
output->push_back(&field);
1801+
}
1802+
};
1803+
// Core functionality difference depending on the value of kForIsEmpty. If we
1804+
// encounter a set field, either return 1, or push it back into the vector.
1805+
#define PROTO_REFLECTION_APPEND_OR_RETURN() \
1806+
do { \
1807+
if constexpr (kForIsEmpty) { \
1808+
return 1; \
1809+
} else { \
1810+
append_to_output(field); \
1811+
} \
1812+
} while (false)
1813+
17871814
int i = -1;
17881815
for (const FieldDescriptor& field :
1789-
absl::MakeSpan(descriptor->fields_, last_non_weak_field_index_ + 1)) {
1816+
absl::MakeSpan(descriptor.fields_, last_non_weak_field_index_ + 1)) {
17901817
++i;
17911818
const OneofDescriptor* containing_oneof = field.containing_oneof();
17921819
// If the hasbits for repeated fields experiment is disabled, we can
@@ -1795,7 +1822,7 @@ void Reflection::ListFields(const Message& message,
17951822
if (!internal::EnableExperimentalHintHasBitsForRepeatedFields() &&
17961823
field.is_repeated()) {
17971824
if (FieldSize(message, &field) > 0) {
1798-
append_to_output(&field);
1825+
PROTO_REFLECTION_APPEND_OR_RETURN();
17991826
}
18001827
} else if (schema_.InRealOneof(&field)) {
18011828
const uint32_t* const oneof_case_array =
@@ -1804,20 +1831,33 @@ void Reflection::ListFields(const Message& message,
18041831
// Equivalent to: HasOneofField(message, field)
18051832
if (static_cast<int64_t>(oneof_case_array[containing_oneof->index()]) ==
18061833
field.number()) {
1807-
append_to_output(&field);
1834+
PROTO_REFLECTION_APPEND_OR_RETURN();
18081835
}
18091836
} else if (has_bits &&
18101837
has_bits_indices[i] != static_cast<uint32_t>(kNoHasbit)) {
18111838
// Equivalent to: HasFieldSingular(message, field)
18121839
if (IsFieldPresentGivenHasbits(message, &field, has_bits,
18131840
has_bits_indices[i])) {
1814-
append_to_output(&field);
1841+
PROTO_REFLECTION_APPEND_OR_RETURN();
18151842
}
18161843
} else if (HasFieldWithHasbits(message, &field)) {
1817-
// Fall back on proto3-style HasBit.
1818-
append_to_output(&field);
1844+
PROTO_REFLECTION_APPEND_OR_RETURN();
18191845
}
18201846
}
1847+
#undef PROTO_REFLECTION_APPEND_OR_RETURN
1848+
// Last will be 0 if no fields were encountered. Otherwise it contains the
1849+
// last encountered field.
1850+
return last;
1851+
}
1852+
1853+
void Reflection::ListFields(const Message& message,
1854+
std::vector<const FieldDescriptor*>* output) const {
1855+
output->clear();
1856+
// Optimization: The default instance never has any fields set.
1857+
if (schema_.IsDefaultInstance(message)) return;
1858+
const Descriptor* const descriptor = descriptor_;
1859+
uint32_t last = IsEmptyOrCollectSetFields</*kForIsEmpty=*/false>(
1860+
message, *descriptor, output);
18211861

18221862
// Descriptors of ExtensionSet are appended in their increasing tag
18231863
// order and they are usually bigger than the field tags so if all fields are
@@ -1845,6 +1885,29 @@ void Reflection::ListFields(const Message& message,
18451885
}
18461886
}
18471887

1888+
bool Reflection::IsEmptyIgnoringUnknownFieldsImpl(
1889+
const Message& message) const {
1890+
if (IsEmptyOrCollectSetFields</*kForIsEmpty=*/true>(message, *descriptor_,
1891+
nullptr) != 0) {
1892+
return false;
1893+
}
1894+
return
1895+
!(schema_.HasExtensionSet() && !GetExtensionSet(message).IsEmpty());
1896+
}
1897+
1898+
bool Reflection::IsEmptyIgnoringUnknownFields(const Message& message) const {
1899+
// Optimization: The default instance never has any fields set.
1900+
if (schema_.IsDefaultInstance(message)) return true;
1901+
return IsEmptyIgnoringUnknownFieldsImpl(message);
1902+
}
1903+
1904+
bool Reflection::IsEmpty(const Message& message) const {
1905+
// Optimization: The default instance never has any fields set.
1906+
if (schema_.IsDefaultInstance(message)) return true;
1907+
return IsEmptyIgnoringUnknownFieldsImpl(message) &&
1908+
GetUnknownFields(message).empty();
1909+
}
1910+
18481911
// -------------------------------------------------------------------
18491912

18501913
#undef DEFINE_PRIMITIVE_ACCESSORS

0 commit comments

Comments
 (0)