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 2066558a61..b7f7ffe6ef 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 @@ package com.google.protobuf; 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 @@ class SmallSortedMap, V> extends AbstractMap { 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 @@ class SmallSortedMap, V> extends AbstractMap { private volatile DescendingEntrySet lazyDescendingEntrySet; private SmallSortedMap() { - this.entryList = Collections.emptyList(); this.overflowEntries = Collections.emptyMap(); this.overflowEntriesDescending = Collections.emptyMap(); } @@ -120,8 +118,8 @@ class SmallSortedMap, V> extends AbstractMap { /** 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 @@ class SmallSortedMap, V> extends AbstractMap { /** @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 @@ class SmallSortedMap, V> extends AbstractMap { @Override public int size() { - return entryList.size() + overflowEntries.size(); + return entriesSize + overflowEntries.size(); } /** @@ -191,7 +194,9 @@ class SmallSortedMap, V> extends AbstractMap { 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 @@ class SmallSortedMap, V> extends AbstractMap { 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 @@ class SmallSortedMap, V> extends AbstractMap { 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 @@ class SmallSortedMap, V> extends AbstractMap { 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 @@ class SmallSortedMap, V> extends AbstractMap { */ 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 @@ class SmallSortedMap, V> extends AbstractMap { 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 @@ class SmallSortedMap, V> extends AbstractMap { 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 @@ class SmallSortedMap, V> extends AbstractMap { @Override public boolean hasNext() { - return (pos + 1) < entryList.size() + return (pos + 1) < entriesSize || (!overflowEntries.isEmpty() && getOverflowIterator().hasNext()); } @@ -507,8 +529,10 @@ class SmallSortedMap, V> extends AbstractMap { 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 @@ class SmallSortedMap, V> extends AbstractMap { nextCalledBeforeRemove = false; checkMutable(); - if (pos < entryList.size()) { + if (pos < entriesSize) { removeArrayEntryAt(pos--); } else { getOverflowIterator().remove(); @@ -547,12 +571,12 @@ class SmallSortedMap, V> extends AbstractMap { */ 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 @@ class SmallSortedMap, V> extends AbstractMap { if (getOverflowIterator().hasNext()) { return getOverflowIterator().next(); } - return entryList.get(--pos); + @SuppressWarnings("unchecked") + Entry e = (Entry) entries[--pos]; + return e; } @Override @@ -621,7 +647,7 @@ class SmallSortedMap, V> extends AbstractMap { 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) {