Optimize MessageDifferencer repeated field comparison and reflection lookups. Repeated field comparison in `MessageDifferencer` incurs significant CPU and heap allocation overhead from dynamic `std::vector` allocations per repeated field, atomic reflection synchronization in `SimpleFieldComparator`, template parsing in `StreamReporter`, and scalar element-by-element iteration across contiguous memory buffers. Introduce a depth-indexed vector pool `match_list_pool_` to eliminate repeated heap reallocations across recursive submessage comparisons. Augment `FieldContext` to pass pre-resolved `Reflection` pointers directly to `SimpleFieldComparator::SimpleCompare`, bypassing atomic synchronization. Optimize `StreamReporter` with direct `PrintRaw` and stack-based scalar formatting, replace two-pass map key checks with single-pass `LookupMapValue`, and add fast-path contiguous buffer mismatch scanning for repeated primitive fields under default comparator settings. PiperOrigin-RevId: 970077641
diff --git a/src/google/protobuf/util/field_comparator.cc b/src/google/protobuf/util/field_comparator.cc index 0736262..04ffe6d 100644 --- a/src/google/protobuf/util/field_comparator.cc +++ b/src/google/protobuf/util/field_comparator.cc
@@ -66,9 +66,15 @@ FieldComparator::ComparisonResult SimpleFieldComparator::SimpleCompare( const Message& message_1, const Message& message_2, const FieldDescriptor* field, int index_1, int index_2, - const util::FieldContext* /*field_context*/) { - const Reflection* reflection_1 = message_1.GetReflection(); - const Reflection* reflection_2 = message_2.GetReflection(); + const util::FieldContext* field_context) { + const Reflection* reflection_1 = + (field_context && field_context->reflection1()) + ? field_context->reflection1() + : message_1.GetReflection(); + const Reflection* reflection_2 = + (field_context && field_context->reflection2()) + ? field_context->reflection2() + : message_2.GetReflection(); switch (field->cpp_type()) { #define COMPARE_FIELD(METHOD) \
diff --git a/src/google/protobuf/util/message_differencer.cc b/src/google/protobuf/util/message_differencer.cc index ae685a4..058db17 100644 --- a/src/google/protobuf/util/message_differencer.cc +++ b/src/google/protobuf/util/message_differencer.cc
@@ -14,9 +14,12 @@ #include <algorithm> #include <cstddef> #include <cstdint> +#include <cstring> #include <functional> #include <limits> #include <memory> +#include <numeric> +#include <type_traits> #include <utility> #include <vector> @@ -217,6 +220,89 @@ } } +// Returns true if the repeated field elements are stored contiguously in memory +// and can be compared using fast-path contiguous memory operations (like +// std::equal or std::memcmp). This is only supported for scalar types under the +// default field comparator settings. 'default_field_comparator' must be +// non-null and represents the comparator settings to check. +bool CanUseContiguousScalarCompare( + const FieldDescriptor* field, + const DefaultFieldComparator* default_field_comparator) { + if (!field->is_repeated() || field->is_map()) { + return false; + } + if (default_field_comparator == nullptr) return false; + switch (field->cpp_type()) { + case FieldDescriptor::CPPTYPE_INT32: + case FieldDescriptor::CPPTYPE_INT64: + case FieldDescriptor::CPPTYPE_UINT32: + case FieldDescriptor::CPPTYPE_UINT64: + case FieldDescriptor::CPPTYPE_BOOL: + case FieldDescriptor::CPPTYPE_ENUM: + return true; + case FieldDescriptor::CPPTYPE_FLOAT: + case FieldDescriptor::CPPTYPE_DOUBLE: + return default_field_comparator->float_comparison() == + SimpleFieldComparator::EXACT && + !default_field_comparator->treat_nan_as_equal(); + default: + return false; + } +} + +// Helper function that dynamically dispatches the repeated field to its +// concrete scalar type and calls the provided callable 'fn' with pointers to +// the contiguous data buffers and their sizes. 'fn' must accept arguments: +// (const T* data1, const T* data2, int size1, int size2) for the scalar type T. +template <typename Fn> +auto DispatchScalarRepeatedField(const Reflection* reflection1, + const Message& message1, + const Reflection* reflection2, + const Message& message2, + const FieldDescriptor* field, Fn&& fn) { + switch (field->cpp_type()) { + case FieldDescriptor::CPPTYPE_INT32: + case FieldDescriptor::CPPTYPE_ENUM: { + const auto& r1 = reflection1->GetRepeatedField<int32_t>(message1, field); + const auto& r2 = reflection2->GetRepeatedField<int32_t>(message2, field); + return fn(r1.data(), r2.data(), r1.size(), r2.size()); + } + case FieldDescriptor::CPPTYPE_INT64: { + const auto& r1 = reflection1->GetRepeatedField<int64_t>(message1, field); + const auto& r2 = reflection2->GetRepeatedField<int64_t>(message2, field); + return fn(r1.data(), r2.data(), r1.size(), r2.size()); + } + case FieldDescriptor::CPPTYPE_UINT32: { + const auto& r1 = reflection1->GetRepeatedField<uint32_t>(message1, field); + const auto& r2 = reflection2->GetRepeatedField<uint32_t>(message2, field); + return fn(r1.data(), r2.data(), r1.size(), r2.size()); + } + case FieldDescriptor::CPPTYPE_UINT64: { + const auto& r1 = reflection1->GetRepeatedField<uint64_t>(message1, field); + const auto& r2 = reflection2->GetRepeatedField<uint64_t>(message2, field); + return fn(r1.data(), r2.data(), r1.size(), r2.size()); + } + case FieldDescriptor::CPPTYPE_FLOAT: { + const auto& r1 = reflection1->GetRepeatedField<float>(message1, field); + const auto& r2 = reflection2->GetRepeatedField<float>(message2, field); + return fn(r1.data(), r2.data(), r1.size(), r2.size()); + } + case FieldDescriptor::CPPTYPE_DOUBLE: { + const auto& r1 = reflection1->GetRepeatedField<double>(message1, field); + const auto& r2 = reflection2->GetRepeatedField<double>(message2, field); + return fn(r1.data(), r2.data(), r1.size(), r2.size()); + } + case FieldDescriptor::CPPTYPE_BOOL: { + const auto& r1 = reflection1->GetRepeatedField<bool>(message1, field); + const auto& r2 = reflection2->GetRepeatedField<bool>(message2, field); + return fn(r1.data(), r2.data(), r1.size(), r2.size()); + } + default: + ABSL_LOG(FATAL) << "Unexpected cpp_type in DispatchScalarRepeatedField: " + << field->cpp_type(); + } +} + void AddSpecificIndex( google::protobuf::util::MessageDifferencer::SpecificField* specific_field, const Message& message, const FieldDescriptor* field, int index) { @@ -1091,16 +1177,7 @@ return false; } - // First pass: check whether the same keys are present. - for (ConstMapIterator it = reflection1->ConstMapBegin(&message1, map_field), - it_end = reflection1->ConstMapEnd(&message1, map_field); - it != it_end; ++it) { - if (!reflection2->ContainsMapKey(message2, map_field, it.GetKey())) { - return false; - } - } - - // Second pass: compare values for matching keys. + // Compare values for matching keys in a single pass. const FieldDescriptor* val_des = map_field->message_type()->map_value(); switch (val_des->cpp_type()) { #define HANDLE_TYPE(CPPTYPE, METHOD, COMPAREMETHOD) \ @@ -1110,7 +1187,10 @@ it_end = reflection1->ConstMapEnd(&message1, map_field); \ it != it_end; ++it) { \ MapValueConstRef value2; \ - reflection2->LookupMapValue(message2, map_field, it.GetKey(), &value2); \ + if (!reflection2->LookupMapValue(message2, map_field, it.GetKey(), \ + &value2)) { \ + return false; \ + } \ if (!comparator->Compare##COMPAREMETHOD(*val_des, \ it.GetValueRef().Get##METHOD(), \ value2.Get##METHOD())) { \ @@ -1133,12 +1213,12 @@ for (ConstMapIterator it = reflection1->ConstMapBegin(&message1, map_field); it != reflection1->ConstMapEnd(&message1, map_field); ++it) { - if (!reflection2->ContainsMapKey(message2, map_field, it.GetKey())) { + MapValueConstRef value2; + if (!reflection2->LookupMapValue(message2, map_field, it.GetKey(), + &value2)) { return false; } bool compare_result; - MapValueConstRef value2; - reflection2->LookupMapValue(message2, map_field, it.GetKey(), &value2); // Append currently compared field to the end of parent_fields. SpecificField specific_value_field; specific_value_field.message1 = &message1; @@ -1236,10 +1316,23 @@ return false; } - // These two list are used for store the index of the correspondent - // element in peer repeated field. - std::vector<int> match_list1; - std::vector<int> match_list2; + if (static_cast<size_t>(repeated_field_depth_) >= match_list_pool_.size()) { + match_list_pool_.resize(repeated_field_depth_ + 1); + } + if (!match_list_pool_[repeated_field_depth_]) { + match_list_pool_[repeated_field_depth_] = std::make_unique<MatchListPair>(); + } + MatchListPair* match_lists = match_list_pool_[repeated_field_depth_].get(); + // These two lists are used to store the index of the corresponding + // element in the peer repeated field. + std::vector<int>& match_list1 = match_lists->match_list1; + std::vector<int>& match_list2 = match_lists->match_list2; + + struct DepthGuard { + int& depth; + ~DepthGuard() { --depth; } + } depth_guard{repeated_field_depth_}; + ++repeated_field_depth_; const MapKeyComparator* key_comparator = GetMapKeyComparator(repeated_field); bool smart_list = IsTreatedAsSmartList(repeated_field); @@ -1247,6 +1340,43 @@ !IsTreatedAsSet(repeated_field) && !IsTreatedAsSmartSet(repeated_field) && !smart_list; + const bool can_scalar_compare = + key_comparator == nullptr && field_comparator_kind_ == kFCDefault && + CanUseContiguousScalarCompare(repeated_field, + field_comparator_.default_impl); + + if (simple_list && can_scalar_compare) { + if (reporter_ == nullptr && !treated_as_subset) { + // count1 == count2 is already checked above. + return DispatchScalarRepeatedField( + reflection1, message1, reflection2, message2, repeated_field, + [](const auto* data1, const auto* data2, int count1, int count2) { + if (count1 == 0) return true; + if constexpr (std::is_same_v<std::decay_t<decltype(*data1)>, + float> || + std::is_same_v<std::decay_t<decltype(*data1)>, + double>) { + return std::equal(data1, data1 + count1, data2); + } else { + return std::memcmp(data1, data2, count1 * sizeof(*data1)) == 0; + } + }); + } + } + + int prefix_len = 0; + if (can_scalar_compare) { + DispatchScalarRepeatedField( + reflection1, message1, reflection2, message2, repeated_field, + [&](const auto* data1, const auto* data2, int count1, int count2) { + const int min_count = std::min(count1, count2); + if (min_count > 0) { + auto [m1, m2] = std::mismatch(data1, data1 + min_count, data2); + prefix_len = static_cast<int>(m1 - data1); + } + }); + } + // For simple lists, we avoid matching repeated field indices, saving the // memory allocations that would otherwise be needed for match_list1 and // match_list2. @@ -1270,7 +1400,13 @@ // At this point, we have already matched pairs of fields (with the reporting // to be done later). Now to check if the paired elements are different. int next_unmatched_index = 0; - for (int i = 0; i < count1; i++) { + int start_i = 0; + if (can_scalar_compare && !report_matches_) { + start_i = prefix_len; + next_unmatched_index = prefix_len; + } + + for (int i = start_i; i < count1; i++) { if (simple_list && i >= count2) { break; } @@ -1337,7 +1473,9 @@ } // Report any remaining additions or deletions. - for (int i = 0; i < count2; ++i) { + const int add_start = + simple_list ? count1 : (can_scalar_compare ? prefix_len : 0); + for (int i = add_start; i < count2; ++i) { if (!simple_list && match_list2[i] != -1) continue; if (simple_list && i < count1) continue; if (!treated_as_subset) { @@ -1352,7 +1490,9 @@ parent_fields->pop_back(); } - for (int i = 0; i < count1; ++i) { + const int del_start = + simple_list ? count2 : (can_scalar_compare ? prefix_len : 0); + for (int i = del_start; i < count1; ++i) { if (!simple_list && match_list1[i] != -1) continue; if (simple_list && i < count2) continue; assert(reporter_ != nullptr); @@ -1378,7 +1518,9 @@ const Message& message1, const Message& message2, int unpacked_any, const FieldDescriptor* field, int index1, int index2, std::vector<SpecificField>* parent_fields) { - FieldContext field_context(parent_fields); + const Reflection* reflection1 = message1.GetReflection(); + const Reflection* reflection2 = message2.GetReflection(); + FieldContext field_context(parent_fields, reflection1, reflection2); FieldComparator::ComparisonResult result = GetFieldComparisonResult( message1, message2, field, index1, index2, &field_context); @@ -1386,8 +1528,6 @@ result == FieldComparator::RECURSE) { // Get the nested messages and compare them using one of the Compare // methods. - const Reflection* reflection1 = message1.GetReflection(); - const Reflection* reflection2 = message2.GetReflection(); const Message& m1 = field->is_repeated() ? reflection1->GetRepeatedMessage(message1, field, index1) @@ -1917,8 +2057,8 @@ // which (hopefully) do not contain further repeated fields. if (count1 == 1 && count2 == 1 && reporter_ == nullptr && key_comparator == nullptr) { - match_list1->at(0) = 0; - match_list2->at(0) = 0; + (*match_list1)[0] = 0; + (*match_list2)[0] = 0; return true; } @@ -1955,16 +2095,44 @@ success = success && (match_count == count1); } else { int start_offset = 0; - // If the two repeated fields are treated as sets, optimize for the case - // where both start with same items stored in the same order. - if (IsTreatedAsSet(repeated_field) || is_treated_as_smart_set || - IsTreatedAsSmartList(repeated_field)) { + const bool is_set_or_smart = IsTreatedAsSet(repeated_field) || + is_treated_as_smart_set || + IsTreatedAsSmartList(repeated_field); + const bool can_simd = is_set_or_smart && key_comparator == nullptr && + field_comparator_kind_ == kFCDefault && + CanUseContiguousScalarCompare( + repeated_field, field_comparator_.default_impl); + + // If the repeated fields are contiguous scalars and we can use the default + // comparator, use SIMD to scan for common prefix and suffix. + if (can_simd) { + DispatchScalarRepeatedField( + message1.GetReflection(), message1, message2.GetReflection(), + message2, repeated_field, + [&](const auto* data1, const auto* data2, int count1, int count2) { + const int min_count = std::min(count1, count2); + int prefix_len = 0; + if (min_count > 0) { + auto [m1, m2] = std::mismatch(data1, data1 + min_count, data2); + prefix_len = static_cast<int>(m1 - data1); + } + if (prefix_len > 0) { + std::iota(match_list1->begin(), match_list1->begin() + prefix_len, + 0); + std::iota(match_list2->begin(), match_list2->begin() + prefix_len, + 0); + } + start_offset = prefix_len; + }); + // If the two repeated fields are treated as sets, optimize for the case + // where both start with same items stored in the same order. + } else if (is_set_or_smart) { start_offset = std::min(count1, count2); for (int i = 0; i < count1 && i < count2; i++) { if (IsMatch(repeated_field, key_comparator, &message1, &message2, unpacked_any, parent_fields, nullptr, i, i)) { - match_list1->at(i) = i; - match_list2->at(i) = i; + (*match_list1)[i] = i; + (*match_list2)[i] = i; } else { start_offset = i; break; @@ -1977,9 +2145,9 @@ int matched_j = -1; for (int j = start_offset; j < count2; j++) { - if (match_list2->at(j) != -1) { + if ((*match_list2)[j] != -1) { if (!is_treated_as_smart_set || num_diffs_list1[i] == 0 || - num_diffs_list1[match_list2->at(j)] == 0) { + num_diffs_list1[(*match_list2)[j]] == 0) { continue; } } @@ -2004,8 +2172,8 @@ if (num_diffs < num_diffs_list1[i]) { // If j has been already matched to some element, ensure the // current num_diffs is smaller. - if (match_list2->at(j) == -1 || - num_diffs < num_diffs_list1[match_list2->at(j)]) { + if ((*match_list2)[j] == -1 || + num_diffs < num_diffs_list1[(*match_list2)[j]]) { num_diffs_list1[i] = num_diffs; match = true; } @@ -2023,13 +2191,13 @@ match = (matched_j != -1); if (match) { - if (is_treated_as_smart_set && match_list2->at(matched_j) != -1) { + if (is_treated_as_smart_set && (*match_list2)[matched_j] != -1) { // This is to revert the previously matched index in list2. - match_list1->at(match_list2->at(matched_j)) = -1; + (*match_list1)[(*match_list2)[matched_j]] = -1; match = false; } - match_list1->at(i) = matched_j; - match_list2->at(matched_j) = i; + (*match_list1)[i] = matched_j; + (*match_list2)[matched_j] = i; } if (!match && reporter == nullptr) return false; success = success && match; @@ -2108,15 +2276,17 @@ } } if (i > 0) { - printer_->Print("."); + printer_->PrintRaw("."); } if (specific_field.field != nullptr) { if (specific_field.field->is_extension()) { - printer_->Print("($name$)", "name", specific_field.field->full_name()); + printer_->PrintRaw("("); + printer_->PrintRaw(specific_field.field->full_name()); + printer_->PrintRaw(")"); } else { printer_->PrintRaw(specific_field.field->name()); if (specific_field.forced_compare_no_presence_) { - printer_->Print(" (added for better PARTIAL comparison)"); + printer_->PrintRaw(" (added for better PARTIAL comparison)"); } } @@ -2125,14 +2295,18 @@ continue; } } else { - printer_->PrintRaw(absl::StrCat(specific_field.unknown_field_number)); + printer_->PrintRaw( + absl::AlphaNum(specific_field.unknown_field_number).Piece()); } if (left_side && specific_field.index >= 0) { - printer_->Print("[$name$]", "name", absl::StrCat(specific_field.index)); + printer_->PrintRaw("["); + printer_->PrintRaw(absl::AlphaNum(specific_field.index).Piece()); + printer_->PrintRaw("]"); } if (!left_side && specific_field.new_index >= 0) { - printer_->Print("[$name$]", "name", - absl::StrCat(specific_field.new_index)); + printer_->PrintRaw("["); + printer_->PrintRaw(absl::AlphaNum(specific_field.new_index).Piece()); + printer_->PrintRaw("]"); } } } @@ -2143,10 +2317,67 @@ const SpecificField& specific_field = field_path.back(); const FieldDescriptor* field = specific_field.field; if (field != nullptr) { - std::string output; int index = left_side ? specific_field.index : specific_field.new_index; + const Reflection* reflection = message.GetReflection(); + switch (field->cpp_type()) { + case FieldDescriptor::CPPTYPE_INT32: { + int32_t val = field->is_repeated() + ? reflection->GetRepeatedInt32(message, field, index) + : reflection->GetInt32(message, field); + printer_->PrintRaw(absl::AlphaNum(val).Piece()); + return; + } + case FieldDescriptor::CPPTYPE_INT64: { + int64_t val = field->is_repeated() + ? reflection->GetRepeatedInt64(message, field, index) + : reflection->GetInt64(message, field); + printer_->PrintRaw(absl::AlphaNum(val).Piece()); + return; + } + case FieldDescriptor::CPPTYPE_UINT32: { + uint32_t val = + field->is_repeated() + ? reflection->GetRepeatedUInt32(message, field, index) + : reflection->GetUInt32(message, field); + printer_->PrintRaw(absl::AlphaNum(val).Piece()); + return; + } + case FieldDescriptor::CPPTYPE_UINT64: { + uint64_t val = + field->is_repeated() + ? reflection->GetRepeatedUInt64(message, field, index) + : reflection->GetUInt64(message, field); + printer_->PrintRaw(absl::AlphaNum(val).Piece()); + return; + } + case FieldDescriptor::CPPTYPE_BOOL: { + bool val = field->is_repeated() + ? reflection->GetRepeatedBool(message, field, index) + : reflection->GetBool(message, field); + printer_->PrintRaw(val ? "true" : "false"); + return; + } + case FieldDescriptor::CPPTYPE_ENUM: { + const EnumValueDescriptor* enum_desc = + field->is_repeated() + ? reflection->GetRepeatedEnum(message, field, index) + : reflection->GetEnum(message, field); + if (enum_desc != nullptr) { + printer_->PrintRaw(enum_desc->name()); + } else { + int val = + field->is_repeated() + ? reflection->GetRepeatedEnumValue(message, field, index) + : reflection->GetEnumValue(message, field); + printer_->PrintRaw(absl::AlphaNum(val).Piece()); + } + return; + } + default: + break; + } + std::string output; if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { - const Reflection* reflection = message.GetReflection(); const Message& field_message = field->is_repeated() ? reflection->GetRepeatedMessage(message, field, index) @@ -2165,13 +2396,15 @@ output = PrintShortTextFormat(field_message); } if (output.empty()) { - printer_->Print("{ }"); + printer_->PrintRaw("{ }"); } else { if ((fd != nullptr) && (fd->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE)) { printer_->PrintRaw(output); } else { - printer_->Print("{ $name$ }", "name", output); + printer_->PrintRaw("{ "); + printer_->PrintRaw(output); + printer_->PrintRaw(" }"); } } } else { @@ -2257,21 +2490,21 @@ void MessageDifferencer::StreamReporter::ReportAdded( const Message& /*message1*/, const Message& message2, const std::vector<SpecificField>& field_path) { - printer_->Print("added: "); + printer_->PrintRaw("added: "); PrintPath(field_path, false); - printer_->Print(": "); + printer_->PrintRaw(": "); PrintValue(message2, field_path, false); - printer_->Print("\n"); // Print for newlines. + printer_->PrintRaw("\n"); // Print for newlines. } void MessageDifferencer::StreamReporter::ReportDeleted( const Message& message1, const Message& /*message2*/, const std::vector<SpecificField>& field_path) { - printer_->Print("deleted: "); + printer_->PrintRaw("deleted: "); PrintPath(field_path, true); - printer_->Print(": "); + printer_->PrintRaw(": "); PrintValue(message1, field_path, true); - printer_->Print("\n"); // Print for newlines + printer_->PrintRaw("\n"); // Print for newlines } void MessageDifferencer::StreamReporter::ReportModified( @@ -2290,55 +2523,55 @@ } } - printer_->Print("modified: "); + printer_->PrintRaw("modified: "); PrintPath(field_path, true); if (CheckPathChanged(field_path)) { - printer_->Print(" -> "); + printer_->PrintRaw(" -> "); PrintPath(field_path, false); } - printer_->Print(": "); + printer_->PrintRaw(": "); PrintValue(message1, field_path, true); - printer_->Print(" -> "); + printer_->PrintRaw(" -> "); PrintValue(message2, field_path, false); - printer_->Print("\n"); // Print for newlines. + printer_->PrintRaw("\n"); // Print for newlines. } void MessageDifferencer::StreamReporter::ReportMoved( const Message& message1, const Message& /*message2*/, const std::vector<SpecificField>& field_path) { - printer_->Print("moved: "); + printer_->PrintRaw("moved: "); PrintPath(field_path, true); - printer_->Print(" -> "); + printer_->PrintRaw(" -> "); PrintPath(field_path, false); - printer_->Print(" : "); + printer_->PrintRaw(" : "); PrintValue(message1, field_path, true); - printer_->Print("\n"); // Print for newlines. + printer_->PrintRaw("\n"); // Print for newlines. } void MessageDifferencer::StreamReporter::ReportMatched( const Message& message1, const Message& /*message2*/, const std::vector<SpecificField>& field_path) { - printer_->Print("matched: "); + printer_->PrintRaw("matched: "); PrintPath(field_path, true); if (CheckPathChanged(field_path)) { - printer_->Print(" -> "); + printer_->PrintRaw(" -> "); PrintPath(field_path, false); } - printer_->Print(" : "); + printer_->PrintRaw(" : "); PrintValue(message1, field_path, true); - printer_->Print("\n"); // Print for newlines. + printer_->PrintRaw("\n"); // Print for newlines. } void MessageDifferencer::StreamReporter::ReportIgnored( const Message& /*message1*/, const Message& /*message2*/, const std::vector<SpecificField>& field_path) { - printer_->Print("ignored: "); + printer_->PrintRaw("ignored: "); PrintPath(field_path, true); if (CheckPathChanged(field_path)) { - printer_->Print(" -> "); + printer_->PrintRaw(" -> "); PrintPath(field_path, false); } - printer_->Print("\n"); // Print for newlines. + printer_->PrintRaw("\n"); // Print for newlines. } void MessageDifferencer::StreamReporter::SetMessages(const Message& message1, @@ -2350,13 +2583,13 @@ void MessageDifferencer::StreamReporter::ReportUnknownFieldIgnored( const Message& /*message1*/, const Message& /*message2*/, const std::vector<SpecificField>& field_path) { - printer_->Print("ignored: "); + printer_->PrintRaw("ignored: "); PrintPath(field_path, true); if (CheckPathChanged(field_path)) { - printer_->Print(" -> "); + printer_->PrintRaw(" -> "); PrintPath(field_path, false); } - printer_->Print("\n"); // Print for newlines. + printer_->PrintRaw("\n"); // Print for newlines. } MessageDifferencer::MapKeyComparator*
diff --git a/src/google/protobuf/util/message_differencer.h b/src/google/protobuf/util/message_differencer.h index d3f0b80..5b83647 100644 --- a/src/google/protobuf/util/message_differencer.h +++ b/src/google/protobuf/util/message_differencer.h
@@ -975,6 +975,19 @@ match_indices_for_smart_list_callback_; MessageDifferencer::UnpackAnyField unpack_any_field_; + + // Holds the indices of matching elements for a repeated field comparison at + // a particular recursion depth. + struct MatchListPair { + std::vector<int> match_list1; + std::vector<int> match_list2; + }; + // A pool of MatchListPair objects used to avoid reallocating match lists + // during recursive submessage comparisons. Indexed by repeated field depth. + std::vector<std::unique_ptr<MatchListPair>> match_list_pool_; + // The current depth of repeated field comparison recursion. Used to index + // into match_list_pool_. + int repeated_field_depth_ = 0; }; // This class provides extra information to the FieldComparator::Compare @@ -982,15 +995,27 @@ class PROTOBUF_EXPORT FieldContext { public: explicit FieldContext( - std::vector<MessageDifferencer::SpecificField>* parent_fields) - : parent_fields_(parent_fields) {} + std::vector<MessageDifferencer::SpecificField>* parent_fields, + const Reflection* reflection1 = nullptr, + const Reflection* reflection2 = nullptr) + : parent_fields_(parent_fields), + reflection1_(reflection1), + reflection2_(reflection2) {} std::vector<MessageDifferencer::SpecificField>* parent_fields() const { return parent_fields_; } + const Reflection* reflection1() const { return reflection1_; } + const Reflection* reflection2() const { return reflection2_; } private: std::vector<MessageDifferencer::SpecificField>* parent_fields_; + // Pre-resolved Reflection pointer for message1, used to avoid redundant + // reflection lookups during comparison. + const Reflection* reflection1_; + // Pre-resolved Reflection pointer for message2, used to avoid redundant + // reflection lookups during comparison. + const Reflection* reflection2_; }; } // namespace util