From 910f62779fa86a3a1f653d0d34bb7d24e42f64be Mon Sep 17 00:00:00 2001 From: Mark Hansen Date: Thu, 1 Aug 2024 16:58:42 -0700 Subject: [PATCH] Inline ArrayList's array into SmallSortedMap Store them as Object[], because you can't have a Entry[], because Entry[] has a generic 'this' over , 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 --- .../com/google/protobuf/SmallSortedMap.java | 112 +++++++++++------- 1 file changed, 69 insertions(+), 43 deletions(-) diff --git a/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java b/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java index 2066558a61d6..b7f7ffe6ef29 100644 --- a/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java +++ b/java/core/src/main/java/com/google/protobuf/SmallSortedMap.java @@ -9,7 +9,6 @@ import java.util.AbstractMap; import java.util.AbstractSet; -import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -64,25 +63,21 @@ class SmallSortedMap, V> extends AbstractMap { * Creates a new instance for mapping FieldDescriptors to their values. The {@link * #makeImmutable()} implementation will convert the List values of any repeated fields to * unmodifiable lists. - * - * @param arraySize The size of the entry array containing the lexicographically smallest - * mappings. */ - static > - SmallSortedMap newFieldMap() { - return new SmallSortedMap() { + static > + SmallSortedMap newFieldMap() { + return new SmallSortedMap() { @Override - @SuppressWarnings("unchecked") public void makeImmutable() { if (!isImmutable()) { for (int i = 0; i < getNumArrayEntries(); i++) { - final Map.Entry entry = getArrayEntryAt(i); + final Map.Entry entry = getArrayEntryAt(i); if (entry.getKey().isRepeated()) { final List value = (List) entry.getValue(); entry.setValue(Collections.unmodifiableList(value)); } } - for (Map.Entry entry : getOverflowEntries()) { + for (Map.Entry entry : getOverflowEntries()) { if (entry.getKey().isRepeated()) { final List value = (List) entry.getValue(); entry.setValue(Collections.unmodifiableList(value)); @@ -99,10 +94,14 @@ static , V> SmallSortedMap newInstanceForTest() { return new SmallSortedMap<>(); } - // The "entry array" is actually a List because generic arrays are not - // allowed. ArrayList also nicely handles the entry shifting on inserts and - // removes. - private List entryList; + // Only has Entry elements inside. + // Can't declare this as Entry[] because Entry is generic, so you get "generic array creation" + // error. Instead, use an Object[], and cast to Entry on read. + // null Object[] means 'empty'. + private Object[] entries; + // Number of elements in entries that are valid, like ArrayList.size. + private int entriesSize; + private Map overflowEntries; private boolean isImmutable; // The EntrySet is a stateless view of the Map. It's initialized the first @@ -112,7 +111,6 @@ static , V> SmallSortedMap newInstanceForTest() { private volatile DescendingEntrySet lazyDescendingEntrySet; private SmallSortedMap() { - this.entryList = Collections.emptyList(); this.overflowEntries = Collections.emptyMap(); this.overflowEntriesDescending = Collections.emptyMap(); } @@ -120,8 +118,8 @@ private SmallSortedMap() { /** Make this map immutable from this point forward. */ public void makeImmutable() { if (!isImmutable) { - // Note: There's no need to wrap the entryList in an unmodifiableList - // because none of the list's accessors are exposed. The iterator() of + // Note: There's no need to wrap the entries in an unmodifiableList + // because none of the array's accessors are exposed. The iterator() of // overflowEntries, on the other hand, is exposed so it must be made // unmodifiable. overflowEntries = @@ -143,12 +141,17 @@ public boolean isImmutable() { /** @return The number of entries in the entry array. */ public int getNumArrayEntries() { - return entryList.size(); + return entriesSize; } /** @return The array entry at the given {@code index}. */ public Map.Entry getArrayEntryAt(int index) { - return entryList.get(index); + if (index >= entriesSize) { + throw new ArrayIndexOutOfBoundsException(index); + } + @SuppressWarnings("unchecked") + Entry e = (Entry) entries[index]; + return e; } /** @return There number of overflow entries. */ @@ -165,7 +168,7 @@ public Iterable> getOverflowEntries() { @Override public int size() { - return entryList.size() + overflowEntries.size(); + return entriesSize + overflowEntries.size(); } /** @@ -191,7 +194,9 @@ public V get(Object o) { final K key = (K) o; final int index = binarySearchInArray(key); if (index >= 0) { - return entryList.get(index).getValue(); + @SuppressWarnings("unchecked") + Entry e = (Entry) entries[index]; + return e.getValue(); } return overflowEntries.get(key); } @@ -202,7 +207,9 @@ public V put(K key, V value) { final int index = binarySearchInArray(key); if (index >= 0) { // Replace existing array entry. - return entryList.get(index).setValue(value); + @SuppressWarnings("unchecked") + Entry e = (Entry) entries[index]; + return e.setValue(value); } ensureEntryArrayMutable(); final int insertionPoint = -(index + 1); @@ -211,20 +218,26 @@ public V put(K key, V value) { return getOverflowEntriesMutable().put(key, value); } // Insert new Entry in array. - if (entryList.size() == DEFAULT_FIELD_MAP_ARRAY_SIZE) { + if (entriesSize == DEFAULT_FIELD_MAP_ARRAY_SIZE) { // Shift the last array entry into overflow. - final Entry lastEntryInArray = entryList.remove(DEFAULT_FIELD_MAP_ARRAY_SIZE - 1); + @SuppressWarnings("unchecked") + final Entry lastEntryInArray = (Entry) entries[DEFAULT_FIELD_MAP_ARRAY_SIZE - 1]; + entriesSize--; getOverflowEntriesMutable().put(lastEntryInArray.getKey(), lastEntryInArray.getValue()); } - entryList.add(insertionPoint, new Entry(key, value)); + System.arraycopy( + entries, insertionPoint, entries, insertionPoint + 1, entries.length - insertionPoint - 1); + entries[insertionPoint] = new Entry(key, value); + entriesSize++; return null; } @Override public void clear() { checkMutable(); - if (!entryList.isEmpty()) { - entryList.clear(); + if (entriesSize != 0) { + entries = null; + entriesSize = 0; } if (!overflowEntries.isEmpty()) { overflowEntries.clear(); @@ -256,12 +269,17 @@ public V remove(Object o) { private V removeArrayEntryAt(int index) { checkMutable(); - final V removed = entryList.remove(index).getValue(); + @SuppressWarnings("unchecked") + final V removed = ((Entry) entries[index]).getValue(); + // shift items across + System.arraycopy(entries, index + 1, entries, index, entriesSize - index - 1); + entriesSize--; if (!overflowEntries.isEmpty()) { // Shift the first entry in the overflow to be the last entry in the // array. final Iterator> iterator = getOverflowEntriesMutable().entrySet().iterator(); - entryList.add(new Entry(iterator.next())); + entries[entriesSize] = new Entry(iterator.next()); + entriesSize++; iterator.remove(); } return removed; @@ -274,13 +292,14 @@ private V removeArrayEntryAt(int index) { */ private int binarySearchInArray(K key) { int left = 0; - int right = entryList.size() - 1; + int right = entriesSize - 1; // Optimization: For the common case in which entries are added in // ascending tag order, check the largest element in the array before // doing a full binary search. if (right >= 0) { - int cmp = key.compareTo(entryList.get(right).getKey()); + @SuppressWarnings("unchecked") + int cmp = key.compareTo(((Entry) entries[right]).getKey()); if (cmp > 0) { return -(right + 2); // Insert point is after "right". } else if (cmp == 0) { @@ -290,7 +309,8 @@ private int binarySearchInArray(K key) { while (left <= right) { int mid = (left + right) / 2; - int cmp = key.compareTo(entryList.get(mid).getKey()); + @SuppressWarnings("unchecked") + int cmp = key.compareTo(((Entry) entries[mid]).getKey()); if (cmp < 0) { right = mid - 1; } else if (cmp > 0) { @@ -344,11 +364,13 @@ private SortedMap getOverflowEntriesMutable() { return (SortedMap) overflowEntries; } - /** Lazily creates the entry list. Any code that adds to the list must first call this method. */ + /** + * Lazily creates the entry array. Any code that adds to the array must first call this method. + */ private void ensureEntryArrayMutable() { checkMutable(); - if (entryList.isEmpty() && !(entryList instanceof ArrayList)) { - entryList = new ArrayList<>(DEFAULT_FIELD_MAP_ARRAY_SIZE); + if (entries == null) { + entries = new Object[DEFAULT_FIELD_MAP_ARRAY_SIZE]; } } @@ -498,7 +520,7 @@ private class EntryIterator implements Iterator> { @Override public boolean hasNext() { - return (pos + 1) < entryList.size() + return (pos + 1) < entriesSize || (!overflowEntries.isEmpty() && getOverflowIterator().hasNext()); } @@ -507,8 +529,10 @@ public Map.Entry next() { nextCalledBeforeRemove = true; // Always increment pos so that we know whether the last returned value // was from the array or from overflow. - if (++pos < entryList.size()) { - return entryList.get(pos); + if (++pos < entriesSize) { + @SuppressWarnings("unchecked") + Entry e = (Entry) entries[pos]; + return e; } return getOverflowIterator().next(); } @@ -521,7 +545,7 @@ public void remove() { nextCalledBeforeRemove = false; checkMutable(); - if (pos < entryList.size()) { + if (pos < entriesSize) { removeArrayEntryAt(pos--); } else { getOverflowIterator().remove(); @@ -547,12 +571,12 @@ private Iterator> getOverflowIterator() { */ private class DescendingEntryIterator implements Iterator> { - private int pos = entryList.size(); + private int pos = entriesSize; private Iterator> lazyOverflowIterator; @Override public boolean hasNext() { - return (pos > 0 && pos <= entryList.size()) || getOverflowIterator().hasNext(); + return (pos > 0 && pos <= entriesSize) || getOverflowIterator().hasNext(); } @Override @@ -560,7 +584,9 @@ public Map.Entry next() { if (getOverflowIterator().hasNext()) { return getOverflowIterator().next(); } - return entryList.get(--pos); + @SuppressWarnings("unchecked") + Entry e = (Entry) entries[--pos]; + return e; } @Override @@ -621,7 +647,7 @@ public int hashCode() { int h = 0; final int listSize = getNumArrayEntries(); for (int i = 0; i < listSize; i++) { - h += entryList.get(i).hashCode(); + h += entries[i].hashCode(); } // Avoid the iterator allocation if possible. if (getNumOverflowEntries() > 0) {