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 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
@ -232,27 +234,24 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
@Override
@Nullable
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);
}
@Override
@Nullable
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);
}
@Override
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);
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) {
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();
if (ref != null || (needsResize && allowResize)) {
lock();
try {
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();
}
if (ref != null || (needsResize)) {
restructure(allowResize, ref);
}
}
private void restructure(boolean allowResize, Reference<K, V> ref) {
boolean needsResize;
lock();
try {
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
// will be purged
needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold);
boolean resizing = false;
int restructureSize = this.references.length;
if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
restructureSize <<= 1;
resizing = true;
// Recalculate taking into account count inside lock and items that
// will be purged
needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold);
boolean resizing = false;
int restructureSize = this.references.length;
if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
restructureSize <<= 1;
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;
}
// 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) {
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]);
}
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
if (resizing) {
this.references = restructured;
this.resizeThreshold = (int) (this.references.length * getLoadFactor());
}
this.count.set(Math.max(countAfterRestructure, 0));
}
finally {
unlock();
// Replace volatile members
if (resizing) {
this.references = restructured;
this.resizeThreshold = (int) (this.references.length * getLoadFactor());
}
this.count.set(Math.max(countAfterRestructure, 0));
}
finally {
unlock();
}
}