Skip to content

Commit 05a8a40

Browse files
mhansencopybara-github
authored andcommitted
Protobuf Lite ArrayLists: Defer allocating backing array until we have some idea how much to allocate.
This avoids allocating a backing array of size 10 when we are adding >10 elements. - If we are adding objects one at a time, inflate a Object[10] and continue. - If we are adding objects from a Collection, assume this is the only collection we are adding, and inflate a `Object[collection.size()]` - If the existing array is non-empty, resize the array by 1.5x exponential growth as usual. There's another small change where if we're addAll(<10 elements), we allocate an exact-sized backing array (e.g. size=3), rather than rounding up to size 10. See android/LiteAllocationTest. I think this is good: this will save memory in the common case of just calling .addAll() once, and if we call addAll twice, we grow still exponentially. But we could decide to avoid this or split it out into its own change. This change involves moving some logic out of GeneratedMessageLite. I think the default size of the backing array is better handled inside ProtobufArrayList than inside GeneratedMessageLite's wrapper function. PiperOrigin-RevId: 673612155
1 parent 4e8469c commit 05a8a40

File tree

8 files changed

+89
-47
lines changed

8 files changed

+89
-47
lines changed

java/core/src/main/java/com/google/protobuf/BooleanArrayList.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.google.protobuf;
99

1010
import static com.google.protobuf.Internal.checkNotNull;
11+
import static java.lang.Math.max;
1112

1213
import com.google.protobuf.Internal.BooleanList;
1314
import java.util.Arrays;
@@ -22,7 +23,9 @@
2223
final class BooleanArrayList extends AbstractProtobufList<Boolean>
2324
implements BooleanList, RandomAccess, PrimitiveNonBoxingCollection {
2425

25-
private static final BooleanArrayList EMPTY_LIST = new BooleanArrayList(new boolean[0], 0, false);
26+
private static final boolean[] EMPTY_ARRAY = new boolean[0];
27+
28+
private static final BooleanArrayList EMPTY_LIST = new BooleanArrayList(EMPTY_ARRAY, 0, false);
2629

2730
public static BooleanArrayList emptyList() {
2831
return EMPTY_LIST;
@@ -39,7 +42,7 @@ public static BooleanArrayList emptyList() {
3942

4043
/** Constructs a new mutable {@code BooleanArrayList} with default capacity. */
4144
BooleanArrayList() {
42-
this(new boolean[DEFAULT_CAPACITY], 0, true);
45+
this(EMPTY_ARRAY, 0, true);
4346
}
4447

4548
/**
@@ -101,7 +104,8 @@ public BooleanList mutableCopyWithCapacity(int capacity) {
101104
if (capacity < size) {
102105
throw new IllegalArgumentException();
103106
}
104-
return new BooleanArrayList(Arrays.copyOf(array, capacity), size, true);
107+
boolean[] newArray = capacity == 0 ? EMPTY_ARRAY : Arrays.copyOf(array, capacity);
108+
return new BooleanArrayList(newArray, size, true);
105109
}
106110

107111
@Override
@@ -258,6 +262,10 @@ void ensureCapacity(int minCapacity) {
258262
if (minCapacity <= array.length) {
259263
return;
260264
}
265+
if (array.length == 0) {
266+
array = new boolean[max(minCapacity, DEFAULT_CAPACITY)];
267+
return;
268+
}
261269
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
262270
// exactly the requested capacity, but must exponentially grow instead. This is similar
263271
// behaviour to ArrayList.
@@ -269,8 +277,8 @@ void ensureCapacity(int minCapacity) {
269277
}
270278

271279
private static int growSize(int previousSize) {
272-
// Resize to 1.5x the size
273-
return ((previousSize * 3) / 2) + 1;
280+
// Resize to 1.5x the size, rounding up to DEFAULT_CAPACITY.
281+
return max(((previousSize * 3) / 2) + 1, DEFAULT_CAPACITY);
274282
}
275283

276284
/**

java/core/src/main/java/com/google/protobuf/DoubleArrayList.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.google.protobuf;
99

1010
import static com.google.protobuf.Internal.checkNotNull;
11+
import static java.lang.Math.max;
1112

1213
import com.google.protobuf.Internal.DoubleList;
1314
import java.util.Arrays;
@@ -22,7 +23,9 @@
2223
final class DoubleArrayList extends AbstractProtobufList<Double>
2324
implements DoubleList, RandomAccess, PrimitiveNonBoxingCollection {
2425

25-
private static final DoubleArrayList EMPTY_LIST = new DoubleArrayList(new double[0], 0, false);
26+
private static final double[] EMPTY_ARRAY = new double[0];
27+
28+
private static final DoubleArrayList EMPTY_LIST = new DoubleArrayList(EMPTY_ARRAY, 0, false);
2629

2730
public static DoubleArrayList emptyList() {
2831
return EMPTY_LIST;
@@ -39,7 +42,7 @@ public static DoubleArrayList emptyList() {
3942

4043
/** Constructs a new mutable {@code DoubleArrayList} with default capacity. */
4144
DoubleArrayList() {
42-
this(new double[DEFAULT_CAPACITY], 0, true);
45+
this(EMPTY_ARRAY, 0, true);
4346
}
4447

4548
/**
@@ -101,7 +104,8 @@ public DoubleList mutableCopyWithCapacity(int capacity) {
101104
if (capacity < size) {
102105
throw new IllegalArgumentException();
103106
}
104-
return new DoubleArrayList(Arrays.copyOf(array, capacity), size, true);
107+
double[] newArray = capacity == 0 ? EMPTY_ARRAY : Arrays.copyOf(array, capacity);
108+
return new DoubleArrayList(newArray, size, true);
105109
}
106110

107111
@Override
@@ -258,6 +262,10 @@ void ensureCapacity(int minCapacity) {
258262
if (minCapacity <= array.length) {
259263
return;
260264
}
265+
if (array.length == 0) {
266+
array = new double[max(minCapacity, DEFAULT_CAPACITY)];
267+
return;
268+
}
261269
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
262270
// exactly the requested capacity, but must exponentially grow instead. This is similar
263271
// behaviour to ArrayList.
@@ -269,8 +277,8 @@ void ensureCapacity(int minCapacity) {
269277
}
270278

271279
private static int growSize(int previousSize) {
272-
// Resize to 1.5x the size
273-
return ((previousSize * 3) / 2) + 1;
280+
// Resize to 1.5x the size, rounding up to DEFAULT_CAPACITY.
281+
return max(((previousSize * 3) / 2) + 1, DEFAULT_CAPACITY);
274282
}
275283

276284
/**

java/core/src/main/java/com/google/protobuf/FloatArrayList.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.google.protobuf;
99

1010
import static com.google.protobuf.Internal.checkNotNull;
11+
import static java.lang.Math.max;
1112

1213
import com.google.protobuf.Internal.FloatList;
1314
import java.util.Arrays;
@@ -22,7 +23,9 @@
2223
final class FloatArrayList extends AbstractProtobufList<Float>
2324
implements FloatList, RandomAccess, PrimitiveNonBoxingCollection {
2425

25-
private static final FloatArrayList EMPTY_LIST = new FloatArrayList(new float[0], 0, false);
26+
private static final float[] EMPTY_ARRAY = new float[0];
27+
28+
private static final FloatArrayList EMPTY_LIST = new FloatArrayList(EMPTY_ARRAY, 0, false);
2629

2730
public static FloatArrayList emptyList() {
2831
return EMPTY_LIST;
@@ -39,7 +42,7 @@ public static FloatArrayList emptyList() {
3942

4043
/** Constructs a new mutable {@code FloatArrayList} with default capacity. */
4144
FloatArrayList() {
42-
this(new float[DEFAULT_CAPACITY], 0, true);
45+
this(EMPTY_ARRAY, 0, true);
4346
}
4447

4548
/**
@@ -100,7 +103,8 @@ public FloatList mutableCopyWithCapacity(int capacity) {
100103
if (capacity < size) {
101104
throw new IllegalArgumentException();
102105
}
103-
return new FloatArrayList(Arrays.copyOf(array, capacity), size, true);
106+
float[] newArray = capacity == 0 ? EMPTY_ARRAY : Arrays.copyOf(array, capacity);
107+
return new FloatArrayList(newArray, size, true);
104108
}
105109

106110
@Override
@@ -257,6 +261,10 @@ void ensureCapacity(int minCapacity) {
257261
if (minCapacity <= array.length) {
258262
return;
259263
}
264+
if (array.length == 0) {
265+
array = new float[max(minCapacity, DEFAULT_CAPACITY)];
266+
return;
267+
}
260268
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
261269
// exactly the requested capacity, but must exponentially grow instead. This is similar
262270
// behaviour to ArrayList.
@@ -268,8 +276,8 @@ void ensureCapacity(int minCapacity) {
268276
}
269277

270278
private static int growSize(int previousSize) {
271-
// Resize to 1.5x the size
272-
return ((previousSize * 3) / 2) + 1;
279+
// Resize to 1.5x the size, rounding up to DEFAULT_CAPACITY.
280+
return max(((previousSize * 3) / 2) + 1, DEFAULT_CAPACITY);
273281
}
274282

275283
/**

java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,8 +1496,7 @@ protected static IntList emptyIntList() {
14961496

14971497
protected static IntList mutableCopy(IntList list) {
14981498
int size = list.size();
1499-
return list.mutableCopyWithCapacity(
1500-
size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
1499+
return list.mutableCopyWithCapacity(size * 2);
15011500
}
15021501

15031502
protected static LongList emptyLongList() {
@@ -1506,8 +1505,7 @@ protected static LongList emptyLongList() {
15061505

15071506
protected static LongList mutableCopy(LongList list) {
15081507
int size = list.size();
1509-
return list.mutableCopyWithCapacity(
1510-
size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
1508+
return list.mutableCopyWithCapacity(size * 2);
15111509
}
15121510

15131511
protected static FloatList emptyFloatList() {
@@ -1516,8 +1514,7 @@ protected static FloatList emptyFloatList() {
15161514

15171515
protected static FloatList mutableCopy(FloatList list) {
15181516
int size = list.size();
1519-
return list.mutableCopyWithCapacity(
1520-
size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
1517+
return list.mutableCopyWithCapacity(size * 2);
15211518
}
15221519

15231520
protected static DoubleList emptyDoubleList() {
@@ -1526,8 +1523,7 @@ protected static DoubleList emptyDoubleList() {
15261523

15271524
protected static DoubleList mutableCopy(DoubleList list) {
15281525
int size = list.size();
1529-
return list.mutableCopyWithCapacity(
1530-
size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
1526+
return list.mutableCopyWithCapacity(size * 2);
15311527
}
15321528

15331529
protected static BooleanList emptyBooleanList() {
@@ -1536,8 +1532,7 @@ protected static BooleanList emptyBooleanList() {
15361532

15371533
protected static BooleanList mutableCopy(BooleanList list) {
15381534
int size = list.size();
1539-
return list.mutableCopyWithCapacity(
1540-
size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
1535+
return list.mutableCopyWithCapacity(size * 2);
15411536
}
15421537

15431538
protected static <E> ProtobufList<E> emptyProtobufList() {
@@ -1546,8 +1541,7 @@ protected static <E> ProtobufList<E> emptyProtobufList() {
15461541

15471542
protected static <E> ProtobufList<E> mutableCopy(ProtobufList<E> list) {
15481543
int size = list.size();
1549-
return list.mutableCopyWithCapacity(
1550-
size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
1544+
return list.mutableCopyWithCapacity(size * 2);
15511545
}
15521546

15531547
/**

java/core/src/main/java/com/google/protobuf/IntArrayList.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.google.protobuf;
99

1010
import static com.google.protobuf.Internal.checkNotNull;
11+
import static java.lang.Math.max;
1112

1213
import com.google.protobuf.Internal.IntList;
1314
import java.util.Arrays;
@@ -22,7 +23,9 @@
2223
final class IntArrayList extends AbstractProtobufList<Integer>
2324
implements IntList, RandomAccess, PrimitiveNonBoxingCollection {
2425

25-
private static final IntArrayList EMPTY_LIST = new IntArrayList(new int[0], 0, false);
26+
private static final int[] EMPTY_ARRAY = new int[0];
27+
28+
private static final IntArrayList EMPTY_LIST = new IntArrayList(EMPTY_ARRAY, 0, false);
2629

2730
public static IntArrayList emptyList() {
2831
return EMPTY_LIST;
@@ -39,7 +42,7 @@ public static IntArrayList emptyList() {
3942

4043
/** Constructs a new mutable {@code IntArrayList} with default capacity. */
4144
IntArrayList() {
42-
this(new int[DEFAULT_CAPACITY], 0, true);
45+
this(EMPTY_ARRAY, 0, true);
4346
}
4447

4548
/**
@@ -100,7 +103,8 @@ public IntList mutableCopyWithCapacity(int capacity) {
100103
if (capacity < size) {
101104
throw new IllegalArgumentException();
102105
}
103-
return new IntArrayList(Arrays.copyOf(array, capacity), size, true);
106+
int[] newArray = capacity == 0 ? EMPTY_ARRAY : Arrays.copyOf(array, capacity);
107+
return new IntArrayList(newArray, size, true);
104108
}
105109

106110
@Override
@@ -257,6 +261,10 @@ void ensureCapacity(int minCapacity) {
257261
if (minCapacity <= array.length) {
258262
return;
259263
}
264+
if (array.length == 0) {
265+
array = new int[max(minCapacity, DEFAULT_CAPACITY)];
266+
return;
267+
}
260268
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
261269
// exactly the requested capacity, but must exponentially grow instead. This is similar
262270
// behaviour to ArrayList.
@@ -268,8 +276,8 @@ void ensureCapacity(int minCapacity) {
268276
}
269277

270278
private static int growSize(int previousSize) {
271-
// Resize to 1.5x the size
272-
return ((previousSize * 3) / 2) + 1;
279+
// Resize to 1.5x the size, rounding up to DEFAULT_CAPACITY.
280+
return max(((previousSize * 3) / 2) + 1, DEFAULT_CAPACITY);
273281
}
274282

275283
/**

java/core/src/main/java/com/google/protobuf/LongArrayList.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package com.google.protobuf;
99

1010
import static com.google.protobuf.Internal.checkNotNull;
11+
import static java.lang.Math.max;
1112

1213
import com.google.protobuf.Internal.LongList;
1314
import java.util.Arrays;
@@ -22,7 +23,9 @@
2223
final class LongArrayList extends AbstractProtobufList<Long>
2324
implements LongList, RandomAccess, PrimitiveNonBoxingCollection {
2425

25-
private static final LongArrayList EMPTY_LIST = new LongArrayList(new long[0], 0, false);
26+
private static final long[] EMPTY_ARRAY = new long[0];
27+
28+
private static final LongArrayList EMPTY_LIST = new LongArrayList(EMPTY_ARRAY, 0, false);
2629

2730
public static LongArrayList emptyList() {
2831
return EMPTY_LIST;
@@ -39,7 +42,7 @@ public static LongArrayList emptyList() {
3942

4043
/** Constructs a new mutable {@code LongArrayList} with default capacity. */
4144
LongArrayList() {
42-
this(new long[DEFAULT_CAPACITY], 0, true);
45+
this(EMPTY_ARRAY, 0, true);
4346
}
4447

4548
/**
@@ -100,7 +103,8 @@ public LongList mutableCopyWithCapacity(int capacity) {
100103
if (capacity < size) {
101104
throw new IllegalArgumentException();
102105
}
103-
return new LongArrayList(Arrays.copyOf(array, capacity), size, true);
106+
long[] newArray = capacity == 0 ? EMPTY_ARRAY : Arrays.copyOf(array, capacity);
107+
return new LongArrayList(newArray, size, true);
104108
}
105109

106110
@Override
@@ -257,6 +261,10 @@ void ensureCapacity(int minCapacity) {
257261
if (minCapacity <= array.length) {
258262
return;
259263
}
264+
if (array.length == 0) {
265+
array = new long[max(minCapacity, DEFAULT_CAPACITY)];
266+
return;
267+
}
260268
// To avoid quadratic copying when calling .addAllFoo(List) in a loop, we must not size to
261269
// exactly the requested capacity, but must exponentially grow instead. This is similar
262270
// behaviour to ArrayList.
@@ -268,8 +276,8 @@ void ensureCapacity(int minCapacity) {
268276
}
269277

270278
private static int growSize(int previousSize) {
271-
// Resize to 1.5x the size
272-
return ((previousSize * 3) / 2) + 1;
279+
// Resize to 1.5x the size, rounding up to DEFAULT_CAPACITY.
280+
return max(((previousSize * 3) / 2) + 1, DEFAULT_CAPACITY);
273281
}
274282

275283
/**

java/core/src/main/java/com/google/protobuf/MessageSchema.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3577,9 +3577,7 @@ private int parseRepeatedField(
35773577
ProtobufList<?> list = (ProtobufList<?>) UNSAFE.getObject(message, fieldOffset);
35783578
if (!list.isModifiable()) {
35793579
final int size = list.size();
3580-
list =
3581-
list.mutableCopyWithCapacity(
3582-
size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
3580+
list = list.mutableCopyWithCapacity(size * 2);
35833581
UNSAFE.putObject(message, fieldOffset, list);
35843582
}
35853583
switch (fieldType) {

0 commit comments

Comments
 (0)