Skip to content

Commit 910f627

Browse files
mhansencopybara-github
authored andcommitted
Inline ArrayList's array into SmallSortedMap
Store them as Object[], because you can't have a Entry[], because Entry[] has a generic 'this' over <K, V>, you get error "generic array creation" if you try to make "new Entry[]". And if you try to cast Object[] to Entry[], you get ClassCastException. So I've just made it an object array that we cast when reading out of. This should save memory and improve performance, because we have fewer pointers to chase. Also fixed some warnings about unnecessary unchecked suppressions, and type-name should end with T. PiperOrigin-RevId: 658585983
1 parent 58a97a4 commit 910f627

File tree

1 file changed

+69
-43
lines changed

1 file changed

+69
-43
lines changed

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

Lines changed: 69 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import java.util.AbstractMap;
1111
import java.util.AbstractSet;
12-
import java.util.ArrayList;
1312
import java.util.Collections;
1413
import java.util.Iterator;
1514
import java.util.List;
@@ -64,25 +63,21 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> {
6463
* Creates a new instance for mapping FieldDescriptors to their values. The {@link
6564
* #makeImmutable()} implementation will convert the List values of any repeated fields to
6665
* unmodifiable lists.
67-
*
68-
* @param arraySize The size of the entry array containing the lexicographically smallest
69-
* mappings.
7066
*/
71-
static <FieldDescriptorType extends FieldSet.FieldDescriptorLite<FieldDescriptorType>>
72-
SmallSortedMap<FieldDescriptorType, Object> newFieldMap() {
73-
return new SmallSortedMap<FieldDescriptorType, Object>() {
67+
static <FieldDescriptorT extends FieldSet.FieldDescriptorLite<FieldDescriptorT>>
68+
SmallSortedMap<FieldDescriptorT, Object> newFieldMap() {
69+
return new SmallSortedMap<FieldDescriptorT, Object>() {
7470
@Override
75-
@SuppressWarnings("unchecked")
7671
public void makeImmutable() {
7772
if (!isImmutable()) {
7873
for (int i = 0; i < getNumArrayEntries(); i++) {
79-
final Map.Entry<FieldDescriptorType, Object> entry = getArrayEntryAt(i);
74+
final Map.Entry<FieldDescriptorT, Object> entry = getArrayEntryAt(i);
8075
if (entry.getKey().isRepeated()) {
8176
final List<?> value = (List) entry.getValue();
8277
entry.setValue(Collections.unmodifiableList(value));
8378
}
8479
}
85-
for (Map.Entry<FieldDescriptorType, Object> entry : getOverflowEntries()) {
80+
for (Map.Entry<FieldDescriptorT, Object> entry : getOverflowEntries()) {
8681
if (entry.getKey().isRepeated()) {
8782
final List<?> value = (List) entry.getValue();
8883
entry.setValue(Collections.unmodifiableList(value));
@@ -99,10 +94,14 @@ static <K extends Comparable<K>, V> SmallSortedMap<K, V> newInstanceForTest() {
9994
return new SmallSortedMap<>();
10095
}
10196

102-
// The "entry array" is actually a List because generic arrays are not
103-
// allowed. ArrayList also nicely handles the entry shifting on inserts and
104-
// removes.
105-
private List<Entry> entryList;
97+
// Only has Entry elements inside.
98+
// Can't declare this as Entry[] because Entry is generic, so you get "generic array creation"
99+
// error. Instead, use an Object[], and cast to Entry on read.
100+
// null Object[] means 'empty'.
101+
private Object[] entries;
102+
// Number of elements in entries that are valid, like ArrayList.size.
103+
private int entriesSize;
104+
106105
private Map<K, V> overflowEntries;
107106
private boolean isImmutable;
108107
// The EntrySet is a stateless view of the Map. It's initialized the first
@@ -112,16 +111,15 @@ static <K extends Comparable<K>, V> SmallSortedMap<K, V> newInstanceForTest() {
112111
private volatile DescendingEntrySet lazyDescendingEntrySet;
113112

114113
private SmallSortedMap() {
115-
this.entryList = Collections.emptyList();
116114
this.overflowEntries = Collections.emptyMap();
117115
this.overflowEntriesDescending = Collections.emptyMap();
118116
}
119117

120118
/** Make this map immutable from this point forward. */
121119
public void makeImmutable() {
122120
if (!isImmutable) {
123-
// Note: There's no need to wrap the entryList in an unmodifiableList
124-
// because none of the list's accessors are exposed. The iterator() of
121+
// Note: There's no need to wrap the entries in an unmodifiableList
122+
// because none of the array's accessors are exposed. The iterator() of
125123
// overflowEntries, on the other hand, is exposed so it must be made
126124
// unmodifiable.
127125
overflowEntries =
@@ -143,12 +141,17 @@ public boolean isImmutable() {
143141

144142
/** @return The number of entries in the entry array. */
145143
public int getNumArrayEntries() {
146-
return entryList.size();
144+
return entriesSize;
147145
}
148146

149147
/** @return The array entry at the given {@code index}. */
150148
public Map.Entry<K, V> getArrayEntryAt(int index) {
151-
return entryList.get(index);
149+
if (index >= entriesSize) {
150+
throw new ArrayIndexOutOfBoundsException(index);
151+
}
152+
@SuppressWarnings("unchecked")
153+
Entry e = (Entry) entries[index];
154+
return e;
152155
}
153156

154157
/** @return There number of overflow entries. */
@@ -165,7 +168,7 @@ public Iterable<Map.Entry<K, V>> getOverflowEntries() {
165168

166169
@Override
167170
public int size() {
168-
return entryList.size() + overflowEntries.size();
171+
return entriesSize + overflowEntries.size();
169172
}
170173

171174
/**
@@ -191,7 +194,9 @@ public V get(Object o) {
191194
final K key = (K) o;
192195
final int index = binarySearchInArray(key);
193196
if (index >= 0) {
194-
return entryList.get(index).getValue();
197+
@SuppressWarnings("unchecked")
198+
Entry e = (Entry) entries[index];
199+
return e.getValue();
195200
}
196201
return overflowEntries.get(key);
197202
}
@@ -202,7 +207,9 @@ public V put(K key, V value) {
202207
final int index = binarySearchInArray(key);
203208
if (index >= 0) {
204209
// Replace existing array entry.
205-
return entryList.get(index).setValue(value);
210+
@SuppressWarnings("unchecked")
211+
Entry e = (Entry) entries[index];
212+
return e.setValue(value);
206213
}
207214
ensureEntryArrayMutable();
208215
final int insertionPoint = -(index + 1);
@@ -211,20 +218,26 @@ public V put(K key, V value) {
211218
return getOverflowEntriesMutable().put(key, value);
212219
}
213220
// Insert new Entry in array.
214-
if (entryList.size() == DEFAULT_FIELD_MAP_ARRAY_SIZE) {
221+
if (entriesSize == DEFAULT_FIELD_MAP_ARRAY_SIZE) {
215222
// Shift the last array entry into overflow.
216-
final Entry lastEntryInArray = entryList.remove(DEFAULT_FIELD_MAP_ARRAY_SIZE - 1);
223+
@SuppressWarnings("unchecked")
224+
final Entry lastEntryInArray = (Entry) entries[DEFAULT_FIELD_MAP_ARRAY_SIZE - 1];
225+
entriesSize--;
217226
getOverflowEntriesMutable().put(lastEntryInArray.getKey(), lastEntryInArray.getValue());
218227
}
219-
entryList.add(insertionPoint, new Entry(key, value));
228+
System.arraycopy(
229+
entries, insertionPoint, entries, insertionPoint + 1, entries.length - insertionPoint - 1);
230+
entries[insertionPoint] = new Entry(key, value);
231+
entriesSize++;
220232
return null;
221233
}
222234

223235
@Override
224236
public void clear() {
225237
checkMutable();
226-
if (!entryList.isEmpty()) {
227-
entryList.clear();
238+
if (entriesSize != 0) {
239+
entries = null;
240+
entriesSize = 0;
228241
}
229242
if (!overflowEntries.isEmpty()) {
230243
overflowEntries.clear();
@@ -256,12 +269,17 @@ public V remove(Object o) {
256269

257270
private V removeArrayEntryAt(int index) {
258271
checkMutable();
259-
final V removed = entryList.remove(index).getValue();
272+
@SuppressWarnings("unchecked")
273+
final V removed = ((Entry) entries[index]).getValue();
274+
// shift items across
275+
System.arraycopy(entries, index + 1, entries, index, entriesSize - index - 1);
276+
entriesSize--;
260277
if (!overflowEntries.isEmpty()) {
261278
// Shift the first entry in the overflow to be the last entry in the
262279
// array.
263280
final Iterator<Map.Entry<K, V>> iterator = getOverflowEntriesMutable().entrySet().iterator();
264-
entryList.add(new Entry(iterator.next()));
281+
entries[entriesSize] = new Entry(iterator.next());
282+
entriesSize++;
265283
iterator.remove();
266284
}
267285
return removed;
@@ -274,13 +292,14 @@ private V removeArrayEntryAt(int index) {
274292
*/
275293
private int binarySearchInArray(K key) {
276294
int left = 0;
277-
int right = entryList.size() - 1;
295+
int right = entriesSize - 1;
278296

279297
// Optimization: For the common case in which entries are added in
280298
// ascending tag order, check the largest element in the array before
281299
// doing a full binary search.
282300
if (right >= 0) {
283-
int cmp = key.compareTo(entryList.get(right).getKey());
301+
@SuppressWarnings("unchecked")
302+
int cmp = key.compareTo(((Entry) entries[right]).getKey());
284303
if (cmp > 0) {
285304
return -(right + 2); // Insert point is after "right".
286305
} else if (cmp == 0) {
@@ -290,7 +309,8 @@ private int binarySearchInArray(K key) {
290309

291310
while (left <= right) {
292311
int mid = (left + right) / 2;
293-
int cmp = key.compareTo(entryList.get(mid).getKey());
312+
@SuppressWarnings("unchecked")
313+
int cmp = key.compareTo(((Entry) entries[mid]).getKey());
294314
if (cmp < 0) {
295315
right = mid - 1;
296316
} else if (cmp > 0) {
@@ -344,11 +364,13 @@ private SortedMap<K, V> getOverflowEntriesMutable() {
344364
return (SortedMap<K, V>) overflowEntries;
345365
}
346366

347-
/** Lazily creates the entry list. Any code that adds to the list must first call this method. */
367+
/**
368+
* Lazily creates the entry array. Any code that adds to the array must first call this method.
369+
*/
348370
private void ensureEntryArrayMutable() {
349371
checkMutable();
350-
if (entryList.isEmpty() && !(entryList instanceof ArrayList)) {
351-
entryList = new ArrayList<>(DEFAULT_FIELD_MAP_ARRAY_SIZE);
372+
if (entries == null) {
373+
entries = new Object[DEFAULT_FIELD_MAP_ARRAY_SIZE];
352374
}
353375
}
354376

@@ -498,7 +520,7 @@ private class EntryIterator implements Iterator<Map.Entry<K, V>> {
498520

499521
@Override
500522
public boolean hasNext() {
501-
return (pos + 1) < entryList.size()
523+
return (pos + 1) < entriesSize
502524
|| (!overflowEntries.isEmpty() && getOverflowIterator().hasNext());
503525
}
504526

@@ -507,8 +529,10 @@ public Map.Entry<K, V> next() {
507529
nextCalledBeforeRemove = true;
508530
// Always increment pos so that we know whether the last returned value
509531
// was from the array or from overflow.
510-
if (++pos < entryList.size()) {
511-
return entryList.get(pos);
532+
if (++pos < entriesSize) {
533+
@SuppressWarnings("unchecked")
534+
Entry e = (Entry) entries[pos];
535+
return e;
512536
}
513537
return getOverflowIterator().next();
514538
}
@@ -521,7 +545,7 @@ public void remove() {
521545
nextCalledBeforeRemove = false;
522546
checkMutable();
523547

524-
if (pos < entryList.size()) {
548+
if (pos < entriesSize) {
525549
removeArrayEntryAt(pos--);
526550
} else {
527551
getOverflowIterator().remove();
@@ -547,20 +571,22 @@ private Iterator<Map.Entry<K, V>> getOverflowIterator() {
547571
*/
548572
private class DescendingEntryIterator implements Iterator<Map.Entry<K, V>> {
549573

550-
private int pos = entryList.size();
574+
private int pos = entriesSize;
551575
private Iterator<Map.Entry<K, V>> lazyOverflowIterator;
552576

553577
@Override
554578
public boolean hasNext() {
555-
return (pos > 0 && pos <= entryList.size()) || getOverflowIterator().hasNext();
579+
return (pos > 0 && pos <= entriesSize) || getOverflowIterator().hasNext();
556580
}
557581

558582
@Override
559583
public Map.Entry<K, V> next() {
560584
if (getOverflowIterator().hasNext()) {
561585
return getOverflowIterator().next();
562586
}
563-
return entryList.get(--pos);
587+
@SuppressWarnings("unchecked")
588+
Entry e = (Entry) entries[--pos];
589+
return e;
564590
}
565591

566592
@Override
@@ -621,7 +647,7 @@ public int hashCode() {
621647
int h = 0;
622648
final int listSize = getNumArrayEntries();
623649
for (int i = 0; i < listSize; i++) {
624-
h += entryList.get(i).hashCode();
650+
h += entries[i].hashCode();
625651
}
626652
// Avoid the iterator allocation if possible.
627653
if (getNumOverflowEntries() > 0) {

0 commit comments

Comments
 (0)