Skip to content

Commit

Permalink
Reserve capacity in ProtobufArrayList when calling Builder.addAllRepe…
Browse files Browse the repository at this point in the history
…atedMessage(Collection)

This avoids some extra copies and garbage generation.

There's still the extra default 10-sized backing array created by default in
ProtobufArrayList which isn't fixed yet, which we see in allocation tests. That's from `ensureXIsMutable()` which allocates a new list without awareness of the list's size. Maybe we can fix that later: b/362848901.

PiperOrigin-RevId: 670766317
  • Loading branch information
mhansen authored and copybara-github committed Sep 4, 2024
1 parent bd1887e commit e3cc31a
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,14 @@ private String getReadingExceptionMessage(String target) {

// We check nulls as we iterate to avoid iterating over values twice.
private static <T> void addAllCheckingNulls(Iterable<T> values, List<? super T> list) {
if (list instanceof ArrayList && values instanceof Collection) {
((ArrayList<T>) list).ensureCapacity(list.size() + ((Collection<T>) values).size());
if (values instanceof Collection) {
int growth = ((Collection<T>) values).size();
if (list instanceof ArrayList) {
((ArrayList<T>) list).ensureCapacity(list.size() + growth);
}
if (list instanceof ProtobufArrayList) {
((ProtobufArrayList<T>) list).ensureCapacity(list.size() + growth);
}
}
int begin = list.size();
if (values instanceof List && values instanceof RandomAccess) {
Expand Down
26 changes: 22 additions & 4 deletions java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ public boolean add(E element) {
ensureIsMutable();

if (size == array.length) {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(size);
E[] newArray = Arrays.copyOf(array, length);

array = newArray;
Expand All @@ -65,6 +64,11 @@ public boolean add(E element) {
return true;
}

private static int growSize(int previousSize) {
// Resize to 1.5x the size
return ((previousSize * 3) / 2) + 1;
}

@Override
public void add(int index, E element) {
ensureIsMutable();
Expand All @@ -77,8 +81,7 @@ public void add(int index, E element) {
// Shift everything over to make room
System.arraycopy(array, index, array, index + 1, size - index);
} else {
// Resize to 1.5x the size
int length = ((size * 3) / 2) + 1;
int length = growSize(size);
E[] newArray = createArray(length);

// Copy the first part directly
Expand Down Expand Up @@ -132,6 +135,21 @@ public int size() {
return size;
}

/** Ensures the backing array can fit at least minCapacity elements. */
void ensureCapacity(int minCapacity) {
if (minCapacity <= array.length) {
return;
}
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
// exactly the requested capacity, but must exponentially grow instead. This is similar
// behaviour to ArrayList.
int n = size;
while (n < minCapacity) {
n = growSize(n);
}
array = Arrays.copyOf(array, n);
}

@SuppressWarnings("unchecked")
private static <E> E[] createArray(int capacity) {
return (E[]) new Object[capacity];
Expand Down

0 comments on commit e3cc31a

Please sign in to comment.