swim.util.HashGenCacheMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swim-util Show documentation
Show all versions of swim-util Show documentation
Extended collection, iterator, and builder interfaces, lightweight cache sets and maps, and other foundational utilities
The newest version!
// Copyright 2015-2024 Nstream, inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package swim.util;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceArray;
/**
* A hashed generational cache map discards the least recently used value
* with the worst hit rate per hash bucket. HashGenCacheMap is a concurrent
* and lock-free LRFU cache, with O(1) access time.
*
* Maintaining four "generations" of cached values per hash bucket, the cache
* discards from the younger generations based on least recent usage, and
* promotes younger generations to older generations based on most frequent
* usage. Cache misses count as negative usage of the older generations,
* biasing the cache against least recently used values with poor hit rates.
*
* The cache soft references the older generations, and weak references the
* younger generations; the garbage collector can reclaim the entire cache,
* but will preferentially wipe the younger cache generations before the older
* cache generations.
*/
public class HashGenCacheMap {
final AtomicReferenceArray> buckets;
volatile long gen4Hits;
volatile long gen3Hits;
volatile long gen2Hits;
volatile long gen1Hits;
volatile long misses;
public HashGenCacheMap(int size) {
this.buckets = new AtomicReferenceArray>(size);
this.gen4Hits = 0L;
this.gen3Hits = 0L;
this.gen2Hits = 0L;
this.gen1Hits = 0L;
this.misses = 0L;
}
public V get(K key) {
if (this.buckets.length() == 0) {
return null;
}
final int index = Math.abs(key.hashCode()) % this.buckets.length();
final HashGenCacheMapBucket bucket = this.buckets.get(index);
if (bucket == null) {
return null;
}
final K gen4Key = bucket.gen4KeyRef.get();
if (gen4Key != null && key.equals(gen4Key)) {
final V gen4Val = bucket.gen4ValRef.get();
if (gen4Val != null) {
HashGenCacheMap.GEN4_HITS.incrementAndGet(this);
HashGenCacheMapBucket.GEN4_WEIGHT.incrementAndGet(bucket);
return gen4Val;
} else {
bucket.gen4KeyRef.clear();
}
}
final K gen3Key = bucket.gen3KeyRef.get();
if (gen3Key != null && key.equals(gen3Key)) {
final V gen3Val = bucket.gen3ValRef.get();
if (gen3Val != null) {
HashGenCacheMap.GEN3_HITS.incrementAndGet(this);
if (HashGenCacheMapBucket.GEN3_WEIGHT.incrementAndGet(bucket) > bucket.gen4Weight) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight));
}
return gen3Val;
} else {
bucket.gen3KeyRef.clear();
}
}
final K gen2Key = bucket.gen2KeyRef.get();
if (gen2Key != null && key.equals(gen2Key)) {
final V gen2Val = bucket.gen2ValRef.get();
if (gen2Val != null) {
HashGenCacheMap.GEN2_HITS.incrementAndGet(this);
if (HashGenCacheMapBucket.GEN2_WEIGHT.incrementAndGet(bucket) > bucket.gen3Weight) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight));
}
return gen2Val;
} else {
bucket.gen2KeyRef.clear();
}
}
final K gen1Key = bucket.gen1KeyRef.get();
if (gen1Key != null && key.equals(gen1Key)) {
final V gen1Val = bucket.gen1ValRef.get();
if (gen1Val != null) {
HashGenCacheMap.GEN1_HITS.incrementAndGet(this);
if (HashGenCacheMapBucket.GEN1_WEIGHT.incrementAndGet(bucket) > bucket.gen2Weight) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight));
}
return gen1Val;
} else {
bucket.gen1KeyRef.clear();
}
}
HashGenCacheMap.MISSES.incrementAndGet(this);
return null;
}
public V put(K key, V value) {
if (this.buckets.length() == 0) {
return value;
}
final int index = Math.abs(key.hashCode()) % this.buckets.length();
HashGenCacheMapBucket bucket = this.buckets.get(index);
if (bucket == null) {
bucket = new HashGenCacheMapBucket();
}
K gen4Key = bucket.gen4KeyRef.get();
if (gen4Key != null && key.equals(gen4Key)) {
final V gen4Val = bucket.gen4ValRef.get();
if (gen4Val != null) {
HashGenCacheMap.GEN4_HITS.incrementAndGet(this);
HashGenCacheMapBucket.GEN4_WEIGHT.incrementAndGet(bucket);
return gen4Val;
} else {
bucket.gen4KeyRef.clear();
gen4Key = null;
}
}
K gen3Key = bucket.gen3KeyRef.get();
if (gen3Key != null && key.equals(gen3Key)) {
final V gen3Val = bucket.gen3ValRef.get();
if (gen3Val != null) {
HashGenCacheMap.GEN3_HITS.incrementAndGet(this);
if (HashGenCacheMapBucket.GEN3_WEIGHT.incrementAndGet(bucket) > bucket.gen4Weight) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight));
}
return gen3Val;
} else {
bucket.gen3KeyRef.clear();
gen3Key = null;
}
}
K gen2Key = bucket.gen2KeyRef.get();
if (gen2Key != null && key.equals(gen2Key)) {
final V gen2Val = bucket.gen2ValRef.get();
if (gen2Val != null) {
HashGenCacheMap.GEN2_HITS.incrementAndGet(this);
if (HashGenCacheMapBucket.GEN2_WEIGHT.incrementAndGet(bucket) > bucket.gen3Weight) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight));
}
return gen2Val;
} else {
bucket.gen2KeyRef.clear();
gen2Key = null;
}
}
K gen1Key = bucket.gen1KeyRef.get();
if (gen1Key != null && key.equals(gen1Key)) {
final V gen1Val = bucket.gen1ValRef.get();
if (gen1Val != null) {
HashGenCacheMap.GEN1_HITS.incrementAndGet(this);
if (HashGenCacheMapBucket.GEN1_WEIGHT.incrementAndGet(bucket) > bucket.gen2Weight) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight));
}
return gen1Val;
} else {
bucket.gen1KeyRef.clear();
gen1Key = null;
}
}
HashGenCacheMap.MISSES.incrementAndGet(this);
if (gen4Key == null) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
new SoftReference(key), new SoftReference(value), 1));
} else if (gen3Key == null) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
new SoftReference(key), new SoftReference(value), 1));
} else if (gen2Key == null) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
new SoftReference(key), new SoftReference(value), 1));
} else if (gen1Key == null) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
new SoftReference(key), new SoftReference(value), 1));
} else {
// Penalize older gens for thrash. Promote gen1 to prevent nacent gens
// from flip-flopping. If sacrificed gen2 was worth keeping, it likely
// would have already been promoted.
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight - 1,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight - 1,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
new SoftReference(key), new SoftReference(value), 1));
}
return value;
}
public boolean weaken(K key) {
if (this.buckets.length() == 0) {
return false;
}
final int index = Math.abs(key.hashCode()) % this.buckets.length();
final HashGenCacheMapBucket bucket = this.buckets.get(index);
if (bucket == null) {
return false;
}
K gen4Key = bucket.gen4KeyRef.get();
if (gen4Key != null && key.equals(gen4Key)) {
final V gen4Val = bucket.gen4ValRef.get();
if (gen4Val != null) {
this.buckets.set(index, new HashGenCacheMapBucket(HashGenCacheMap.weakRef(bucket.gen4KeyRef), HashGenCacheMap.weakRef(bucket.gen4ValRef), bucket.gen4Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight));
return true;
} else {
bucket.gen4KeyRef.clear();
gen4Key = null;
}
}
K gen3Key = bucket.gen3KeyRef.get();
if (gen3Key != null && key.equals(gen3Key)) {
final V gen3Val = bucket.gen3ValRef.get();
if (gen3Val != null) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
HashGenCacheMap.weakRef(bucket.gen3KeyRef), HashGenCacheMap.weakRef(bucket.gen3ValRef), bucket.gen3Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight));
return true;
} else {
bucket.gen3KeyRef.clear();
gen3Key = null;
}
}
K gen2Key = bucket.gen2KeyRef.get();
if (gen2Key != null && key.equals(gen2Key)) {
final V gen2Val = bucket.gen2ValRef.get();
if (gen2Val != null) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
HashGenCacheMap.weakRef(bucket.gen2KeyRef), HashGenCacheMap.weakRef(bucket.gen2ValRef), bucket.gen2Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight));
return true;
} else {
bucket.gen2KeyRef.clear();
gen2Key = null;
}
}
K gen1Key = bucket.gen1KeyRef.get();
if (gen1Key != null && key.equals(gen1Key)) {
final V gen1Val = bucket.gen1ValRef.get();
if (gen1Val != null) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
HashGenCacheMap.weakRef(bucket.gen1KeyRef), HashGenCacheMap.weakRef(bucket.gen1ValRef), bucket.gen1Weight));
return true;
} else {
bucket.gen1KeyRef.clear();
gen1Key = null;
}
}
if (gen4Key == null) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
HashGenCacheMap.nullRef(), HashGenCacheMap.nullRef(), 1));
} else if (gen3Key == null) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
HashGenCacheMap.nullRef(), HashGenCacheMap.nullRef(), 1));
} else if (gen2Key == null) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
HashGenCacheMap.nullRef(), HashGenCacheMap.nullRef(), 1));
} else if (gen1Key == null) {
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
HashGenCacheMap.nullRef(), HashGenCacheMap.nullRef(), 1));
}
return false;
}
public V remove(K key) {
if (this.buckets.length() == 0) {
return null;
}
final int index = Math.abs(key.hashCode()) % this.buckets.length();
final HashGenCacheMapBucket bucket = this.buckets.get(index);
if (bucket == null) {
return null;
}
final K gen4Key = bucket.gen4KeyRef.get();
if (gen4Key != null && key.equals(gen4Key)) {
final V gen4Val = bucket.gen4ValRef.get();
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
HashGenCacheMap.nullRef(), HashGenCacheMap.nullRef(), 0));
return gen4Val;
}
final K gen3Key = bucket.gen3KeyRef.get();
if (gen3Key != null && key.equals(gen3Key)) {
final V gen3Val = bucket.gen3ValRef.get();
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
HashGenCacheMap.nullRef(), HashGenCacheMap.nullRef(), 0));
return gen3Val;
}
final K gen2Key = bucket.gen2KeyRef.get();
if (gen2Key != null && key.equals(gen2Key)) {
final V gen2Val = bucket.gen2ValRef.get();
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen1KeyRef, bucket.gen1ValRef, bucket.gen1Weight,
HashGenCacheMap.nullRef(), HashGenCacheMap.nullRef(), 0));
return gen2Val;
}
final K gen1Key = bucket.gen1KeyRef.get();
if (gen1Key != null && key.equals(gen1Key)) {
final V gen1Val = bucket.gen1ValRef.get();
this.buckets.set(index, new HashGenCacheMapBucket(bucket.gen4KeyRef, bucket.gen4ValRef, bucket.gen4Weight,
bucket.gen3KeyRef, bucket.gen3ValRef, bucket.gen3Weight,
bucket.gen2KeyRef, bucket.gen2ValRef, bucket.gen2Weight,
HashGenCacheMap.nullRef(), HashGenCacheMap.nullRef(), 0));
return gen1Val;
}
return null;
}
public void clear() {
for (int i = 0; i < this.buckets.length(); i += 1) {
this.buckets.set(i, null);
}
}
public double hitRatio() {
final double hits = (double) HashGenCacheMap.GEN4_HITS.get(this)
+ (double) HashGenCacheMap.GEN3_HITS.get(this)
+ (double) HashGenCacheMap.GEN2_HITS.get(this)
+ (double) HashGenCacheMap.GEN1_HITS.get(this);
return hits / (hits + (double) HashGenCacheMap.MISSES.get(this));
}
@SuppressWarnings("unchecked")
static final AtomicLongFieldUpdater> GEN4_HITS =
AtomicLongFieldUpdater.newUpdater((Class>) (Class>) HashGenCacheMap.class, "gen4Hits");
@SuppressWarnings("unchecked")
static final AtomicLongFieldUpdater> GEN3_HITS =
AtomicLongFieldUpdater.newUpdater((Class>) (Class>) HashGenCacheMap.class, "gen3Hits");
@SuppressWarnings("unchecked")
static final AtomicLongFieldUpdater> GEN2_HITS =
AtomicLongFieldUpdater.newUpdater((Class>) (Class>) HashGenCacheMap.class, "gen2Hits");
@SuppressWarnings("unchecked")
static final AtomicLongFieldUpdater> GEN1_HITS =
AtomicLongFieldUpdater.newUpdater((Class>) (Class>) HashGenCacheMap.class, "gen1Hits");
@SuppressWarnings("unchecked")
static final AtomicLongFieldUpdater> MISSES =
AtomicLongFieldUpdater.newUpdater((Class>) (Class>) HashGenCacheMap.class, "misses");
static final SoftReference