Make ConcurrentReferenceHashMap more JIT friendly

Update `ConcurrentReferenceHashMap` to make some methods more inline
friendly, and to manually inline a few others. These minor optimizations
don't make a great deal of difference for most applications, but seem
worthwhile since we use `ConcurrentReferenceHashMap` for many internal
caches.

Closes gh-22566
This commit is contained in:
Phillip Webb 2019-03-08 17:19:01 -08:00 committed by Juergen Hoeller
parent aa4e56b251
commit e3a86be122
1 changed files with 63 additions and 59 deletions

View File

@ -35,6 +35,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap.Reference;
import org.springframework.util.ConcurrentReferenceHashMap.Restructure;
/** /**
* A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or * A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or
@ -232,27 +234,24 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
@Override @Override
@Nullable @Nullable
public V get(@Nullable Object key) { public V get(@Nullable Object key) {
Entry<K, V> entry = getEntryIfAvailable(key); Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
Entry<K, V> entry = (ref != null ? ref.get() : null);
return (entry != null ? entry.getValue() : null); return (entry != null ? entry.getValue() : null);
} }
@Override @Override
@Nullable @Nullable
public V getOrDefault(@Nullable Object key, @Nullable V defaultValue) { public V getOrDefault(@Nullable Object key, @Nullable V defaultValue) {
Entry<K, V> entry = getEntryIfAvailable(key); Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
Entry<K, V> entry = (ref != null ? ref.get() : null);
return (entry != null ? entry.getValue() : defaultValue); return (entry != null ? entry.getValue() : defaultValue);
} }
@Override @Override
public boolean containsKey(@Nullable Object key) { public boolean containsKey(@Nullable Object key) {
Entry<K, V> entry = getEntryIfAvailable(key);
return (entry != null && ObjectUtils.nullSafeEquals(entry.getKey(), key));
}
@Nullable
private Entry<K, V> getEntryIfAvailable(@Nullable Object key) {
Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY); Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
return (ref != null ? ref.get() : null); Entry<K, V> entry = (ref != null ? ref.get() : null);
return (entry != null && ObjectUtils.nullSafeEquals(entry.getKey(), key));
} }
/** /**
@ -573,65 +572,70 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
*/ */
protected final void restructureIfNecessary(boolean allowResize) { protected final void restructureIfNecessary(boolean allowResize) {
int currCount = this.count.get(); int currCount = this.count.get();
boolean needsResize = (currCount > 0 && currCount >= this.resizeThreshold); boolean needsResize = allowResize && (currCount > 0 && currCount >= this.resizeThreshold);
Reference<K, V> ref = this.referenceManager.pollForPurge(); Reference<K, V> ref = this.referenceManager.pollForPurge();
if (ref != null || (needsResize && allowResize)) { if (ref != null || (needsResize)) {
lock(); restructure(allowResize, ref);
try { }
int countAfterRestructure = this.count.get(); }
Set<Reference<K, V>> toPurge = Collections.emptySet();
if (ref != null) { private void restructure(boolean allowResize, Reference<K, V> ref) {
toPurge = new HashSet<>(); boolean needsResize;
while (ref != null) { lock();
toPurge.add(ref); try {
ref = this.referenceManager.pollForPurge(); int countAfterRestructure = this.count.get();
} Set<Reference<K, V>> toPurge = Collections.emptySet();
if (ref != null) {
toPurge = new HashSet<>();
while (ref != null) {
toPurge.add(ref);
ref = this.referenceManager.pollForPurge();
} }
countAfterRestructure -= toPurge.size(); }
countAfterRestructure -= toPurge.size();
// Recalculate taking into account count inside lock and items that // Recalculate taking into account count inside lock and items that
// will be purged // will be purged
needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold); needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold);
boolean resizing = false; boolean resizing = false;
int restructureSize = this.references.length; int restructureSize = this.references.length;
if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) { if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
restructureSize <<= 1; restructureSize <<= 1;
resizing = true; resizing = true;
}
// Either create a new table or reuse the existing one
Reference<K, V>[] restructured =
(resizing ? createReferenceArray(restructureSize) : this.references);
// Restructure
for (int i = 0; i < this.references.length; i++) {
ref = this.references[i];
if (!resizing) {
restructured[i] = null;
} }
while (ref != null) {
// Either create a new table or reuse the existing one if (!toPurge.contains(ref)) {
Reference<K, V>[] restructured = Entry<K, V> entry = ref.get();
(resizing ? createReferenceArray(restructureSize) : this.references); if (entry != null) {
int index = getIndex(ref.getHash(), restructured);
// Restructure restructured[index] = this.referenceManager.createReference(
for (int i = 0; i < this.references.length; i++) { entry, ref.getHash(), restructured[index]);
ref = this.references[i];
if (!resizing) {
restructured[i] = null;
}
while (ref != null) {
if (!toPurge.contains(ref)) {
Entry<K, V> entry = ref.get();
if (entry != null) {
int index = getIndex(ref.getHash(), restructured);
restructured[index] = this.referenceManager.createReference(
entry, ref.getHash(), restructured[index]);
}
} }
ref = ref.getNext();
} }
ref = ref.getNext();
} }
}
// Replace volatile members // Replace volatile members
if (resizing) { if (resizing) {
this.references = restructured; this.references = restructured;
this.resizeThreshold = (int) (this.references.length * getLoadFactor()); this.resizeThreshold = (int) (this.references.length * getLoadFactor());
}
this.count.set(Math.max(countAfterRestructure, 0));
}
finally {
unlock();
} }
this.count.set(Math.max(countAfterRestructure, 0));
}
finally {
unlock();
} }
} }