Optimize RepeatedPtrField to use copy constructors where possible

The most common MergeFrom() scenario is that items are newly appended to an existing repeated field that has never been shrunk. For these cases copy constructing new entries is the cheapest option. This change overloads MergeFrom<> for `std::string`, `StringPiece`, `Message` and `MessageLite`, leaving the remaining templated implementation for all concrete message types supporting arena enabled copy construction, which is implemented as an out of line factory functor implementation.

Going forward we should change `Arena::CreateMaybeMessage()` to return MessageLite* and accept const MessageLite& for copy construction. This will allow us to directly pass Arena::CreateMaybeMessage<T>() into these out of line functions.

PiperOrigin-RevId: 568220325
pull/14198/head
Martijn Vels 1 year ago committed by Copybara-Service
parent cce7fc4f06
commit 617e1912b2
  1. 3
      src/google/protobuf/implicit_weak_message.h
  2. 91
      src/google/protobuf/repeated_ptr_field.cc
  3. 125
      src/google/protobuf/repeated_ptr_field.h

@ -186,7 +186,8 @@ struct WeakRepeatedPtrField {
T* Add() { return weak.Add(); }
void Clear() { base().template Clear<TypeHandler>(); }
void MergeFrom(const WeakRepeatedPtrField& other) {
base().template MergeFrom<TypeHandler>(other.base());
if (other.empty()) return;
base().template MergeFrom<MessageLite>(other.base());
}
void InternalSwap(WeakRepeatedPtrField* PROTOBUF_RESTRICT other) {
base().InternalSwap(&other->base());

@ -9,10 +9,12 @@
// Based on original Protocol Buffers design by
// Sanjay Ghemawat, Jeff Dean, and others.
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <string>
#include "absl/log/absl_check.h"
#include "google/protobuf/arena.h"
@ -28,7 +30,7 @@ namespace protobuf {
namespace internal {
void RepeatedPtrFieldBase::InternalExtend(int extend_amount) {
void** RepeatedPtrFieldBase::InternalExtend(int extend_amount) {
ABSL_DCHECK(extend_amount > 0);
constexpr size_t ptr_size = sizeof(rep()->elements[0]);
int new_capacity = total_size_ + extend_amount;
@ -74,6 +76,7 @@ void RepeatedPtrFieldBase::InternalExtend(int extend_amount) {
tagged_rep_or_elem_ =
reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(new_rep) + 1);
total_size_ = new_capacity;
return &new_rep->elements[current_size_];
}
void RepeatedPtrFieldBase::Reserve(int capacity) {
@ -151,8 +154,92 @@ template PROTOBUF_EXPORT_TEMPLATE_DEFINE void
memswap<ArenaOffsetHelper<RepeatedPtrFieldBase>::value>(
char* PROTOBUF_RESTRICT, char* PROTOBUF_RESTRICT);
} // namespace internal
template <>
void RepeatedPtrFieldBase::MergeFrom<std::string>(
const RepeatedPtrFieldBase& from) {
ABSL_DCHECK_NE(&from, this);
int new_size = current_size_ + from.current_size_;
auto dst = reinterpret_cast<std::string**>(InternalReserve(new_size));
auto src = reinterpret_cast<std::string* const*>(from.elements());
auto end = src + from.current_size_;
auto end_assign = src + std::min(ClearedCount(), from.current_size_);
for (; src < end_assign; ++dst, ++src) {
(*dst)->assign(**src);
}
if (Arena* const arena = arena_) {
for (; src < end; ++dst, ++src) {
*dst = Arena::Create<std::string>(arena, **src);
}
} else {
for (; src < end; ++dst, ++src) {
*dst = new std::string(**src);
}
}
ExchangeCurrentSize(new_size);
if (new_size > allocated_size()) {
rep()->allocated_size = new_size;
}
}
int RepeatedPtrFieldBase::MergeIntoClearedMessages(
const RepeatedPtrFieldBase& from) {
auto dst = reinterpret_cast<MessageLite**>(elements() + current_size_);
auto src = reinterpret_cast<MessageLite* const*>(from.elements());
int count = std::min(ClearedCount(), from.current_size_);
for (int i = 0; i < count; ++i) {
dst[i]->CheckTypeAndMergeFrom(*src[i]);
}
return count;
}
void RepeatedPtrFieldBase::MergeFromConcreteMessage(
const RepeatedPtrFieldBase& from, CopyFn copy_fn) {
ABSL_DCHECK_NE(&from, this);
int new_size = current_size_ + from.current_size_;
auto dst = reinterpret_cast<MessageLite**>(InternalReserve(new_size));
auto src = reinterpret_cast<MessageLite const* const*>(from.elements());
auto end = src + from.current_size_;
if (PROTOBUF_PREDICT_FALSE(ClearedCount() > 0)) {
int recycled = MergeIntoClearedMessages(from);
dst += recycled;
src += recycled;
}
Arena* arena = GetOwningArena();
for (; src < end; ++src, ++dst) {
*dst = copy_fn(arena, **src);
}
ExchangeCurrentSize(new_size);
if (new_size > allocated_size()) {
rep()->allocated_size = new_size;
}
}
template <>
void RepeatedPtrFieldBase::MergeFrom<MessageLite>(
const RepeatedPtrFieldBase& from) {
ABSL_DCHECK_NE(&from, this);
int new_size = current_size_ + from.current_size_;
auto dst = reinterpret_cast<MessageLite**>(InternalReserve(new_size));
auto src = reinterpret_cast<MessageLite const* const*>(from.elements());
auto end = src + from.current_size_;
if (PROTOBUF_PREDICT_FALSE(ClearedCount() > 0)) {
int recycled = MergeIntoClearedMessages(from);
dst += recycled;
src += recycled;
}
Arena* arena = GetOwningArena();
for (; src < end; ++src, ++dst) {
*dst = (*src)->New(arena);
(*dst)->CheckTypeAndMergeFrom(**src);
}
ExchangeCurrentSize(new_size);
if (new_size > allocated_size()) {
rep()->allocated_size = new_size;
}
}
} // namespace internal
} // namespace protobuf
} // namespace google

@ -296,18 +296,17 @@ class PROTOBUF_EXPORT RepeatedPtrFieldBase {
}
}
template <typename TypeHandler>
void MergeFrom(const RepeatedPtrFieldBase& other) {
// To avoid unnecessary code duplication and reduce binary size, we use a
// layered approach to implementing MergeFrom(). The toplevel method is
// templated, so we get a small thunk per concrete message type in the
// binary. This calls a shared implementation with most of the logic,
// passing a function pointer to another type-specific piece of code that
// calls the object-allocate and merge handlers.
ABSL_DCHECK_NE(&other, this);
if (other.current_size_ == 0) return;
using H = CommonHandler<TypeHandler>;
MergeFromInternal(other, &RepeatedPtrFieldBase::MergeFromInnerLoop<H>);
// Message creating functor: used in MergeFrom<T>()
template <typename T>
static MessageLite* CopyMessage(Arena* arena, const MessageLite& src) {
return Arena::CreateMaybeMessage<T>(arena, static_cast<const T&>(src));
}
// Appends all message values from `from` to this instance.
template <typename T>
void MergeFrom(const RepeatedPtrFieldBase& from) {
static_assert(std::is_base_of<MessageLite, T>::value, "");
MergeFromConcreteMessage(from, CopyMessage<T>);
}
inline void InternalSwap(RepeatedPtrFieldBase* PROTOBUF_RESTRICT rhs) {
@ -348,7 +347,8 @@ class PROTOBUF_EXPORT RepeatedPtrFieldBase {
void CopyFrom(const RepeatedPtrFieldBase& other) {
if (&other == this) return;
RepeatedPtrFieldBase::Clear<TypeHandler>();
RepeatedPtrFieldBase::MergeFrom<TypeHandler>(other);
if (other.empty()) return;
RepeatedPtrFieldBase::MergeFrom<typename TypeHandler::Type>(other);
}
void CloseGap(int start, int num);
@ -629,9 +629,10 @@ class PROTOBUF_EXPORT RepeatedPtrFieldBase {
// temporary on |other|'s arena so that messages are copied twice rather
// than three times.
RepeatedPtrFieldBase temp(other->GetOwningArena());
temp.MergeFrom<TypeHandler>(*this);
this->Clear<TypeHandler>();
this->MergeFrom<TypeHandler>(*other);
if (!this->empty()) {
temp.MergeFrom<typename TypeHandler::Type>(*this);
}
this->CopyFrom<TypeHandler>(*other);
other->InternalSwap(&temp);
temp.Destroy<TypeHandler>(); // Frees rep_ if `other` had no arena.
}
@ -683,6 +684,12 @@ class PROTOBUF_EXPORT RepeatedPtrFieldBase {
friend class google::protobuf::Reflection;
friend class internal::SwapFieldHelper;
// Concrete Arena enabled copy function used to copy messages instances.
// This follows the `Arena::CreateMaybeMessage` signature so that the compiler
// can have the inlined call into the out of line copy function(s) simply pass
// the address of `Arena::CreateMaybeMessage` 'as is'.
using CopyFn = MessageLite* (*)(Arena*, const MessageLite&);
struct Rep {
int allocated_size;
// Here we declare a huge array as a way of approximating C's "flexible
@ -763,52 +770,24 @@ class PROTOBUF_EXPORT RepeatedPtrFieldBase {
ExchangeCurrentSize(0);
}
// Non-templated inner function to avoid code duplication. Takes a function
// pointer to the type-specific (templated) inner allocate/merge loop.
PROTOBUF_NOINLINE void MergeFromInternal(
const RepeatedPtrFieldBase& other,
void (RepeatedPtrFieldBase::*inner_loop)(void**, void* const*, int,
int)) {
// Note: wrapper has already guaranteed that `other_size` > 0.
int other_size = other.current_size_;
Reserve(current_size_ + other_size);
void* const* other_elements = other.elements();
void** new_elements = elements() + current_size_;
int allocated_elems = allocated_size() - current_size_;
(this->*inner_loop)(new_elements, other_elements, other_size,
allocated_elems);
ExchangeCurrentSize(current_size_ + other_size);
if (allocated_size() < current_size_) {
rep()->allocated_size = current_size_;
}
}
// Merges messages from `from` into available, cleared messages sitting in the
// range `[size(), allocated_size())`. Returns the number of message merged
// which is `ClearedCount(), from.size())`.
// Note that this function does explicitly NOT update `current_size_`.
// This function is out of line as it should be the slow path: this scenario
// only happens when a caller constructs and fills a repeated field, then
// shrinks it, and then merges additional messages into it.
int MergeIntoClearedMessages(const RepeatedPtrFieldBase& from);
// Merges other_elems to our_elems.
template <typename TypeHandler>
PROTOBUF_NOINLINE void MergeFromInnerLoop(void** our_elems,
void* const* other_elems,
int length, int already_allocated) {
if (already_allocated < length) {
Arena* arena = GetOwningArena();
auto* elem_prototype = cast<TypeHandler>(other_elems[0]);
for (int i = already_allocated; i < length; i++) {
// Allocate a new empty element that we'll merge into below
our_elems[i] = TypeHandler::NewFromPrototype(elem_prototype, arena);
}
}
// Main loop that does the actual merging
for (int i = 0; i < length; i++) {
// Already allocated: use existing element.
auto* other_elem = cast<TypeHandler>(other_elems[i]);
auto* new_elem = cast<TypeHandler>(our_elems[i]);
TypeHandler::Merge(*other_elem, new_elem);
}
}
// Appends all messages from `from` to this instance, using the
// provided `copy_fn` copy function to copy existing messages.
void MergeFromConcreteMessage(const RepeatedPtrFieldBase& from,
CopyFn copy_fn);
// Extends capacity by at least |extend_amount|.
//
// Pre-condition: |extend_amount| must be > 0.
void InternalExtend(int extend_amount);
void** InternalExtend(int extend_amount);
// Ensures that capacity is big enough to store one more allocated element.
inline void MaybeExtend() {
@ -821,6 +800,16 @@ class PROTOBUF_EXPORT RepeatedPtrFieldBase {
}
}
// Ensures that capacity is at least `n` elements.
// Returns a pointer to the element directly beyond the last element.
inline void** InternalReserve(int n) {
if (n <= total_size_) {
void** elements = using_sso() ? &tagged_rep_or_elem_ : rep()->elements;
return elements + current_size_;
}
return InternalExtend(n - total_size_);
}
// Internal helper for Add: adds "obj" as the next element in the
// array, including potentially resizing the array with Reserve if
// needed
@ -843,6 +832,25 @@ class PROTOBUF_EXPORT RepeatedPtrFieldBase {
Arena* arena_;
};
// Appends all message values from `from` to this instance using the abstract
// message interface. This overload is used in places like reflection and
// other locations where the underlying type is unavailable
template <>
void RepeatedPtrFieldBase::MergeFrom<MessageLite>(
const RepeatedPtrFieldBase& from);
template <>
inline void RepeatedPtrFieldBase::MergeFrom<Message>(
const RepeatedPtrFieldBase& from) {
return MergeFrom<MessageLite>(from);
}
// Appends all `std::string` values from `from` to this instance.
template <>
void RepeatedPtrFieldBase::MergeFrom<std::string>(
const RepeatedPtrFieldBase& from);
void InternalOutOfLineDeleteMessageLite(MessageLite* message);
template <typename GenericType>
@ -1579,7 +1587,8 @@ inline void RepeatedPtrField<Element>::Clear() {
template <typename Element>
inline void RepeatedPtrField<Element>::MergeFrom(
const RepeatedPtrField& other) {
RepeatedPtrFieldBase::MergeFrom<TypeHandler>(other);
if (other.empty()) return;
RepeatedPtrFieldBase::MergeFrom<Element>(other);
}
template <typename Element>

Loading…
Cancel
Save