All Downloads are FREE. Search and download functionalities are using the official Maven repository.

swim.util.HashGenMap Maven / Gradle / Ivy

Go to download

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.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceArray;

/**
 * A hashed generational map evicts the least recently used value with the
 * worst hit rate per hash bucket. HashGenMap is a concurrent and lock-free
 * LRFU cache, with O(1) access time, that strongly references its values.
 * 

* 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 evict(K, V) method is guaranteed to be called when a value is displaced * from the cache. */ public class HashGenMap { final AtomicReferenceArray> buckets; volatile long gen4Hits; volatile long gen3Hits; volatile long gen2Hits; volatile long gen1Hits; volatile long misses; volatile long evicts; public HashGenMap(int size) { this.buckets = new AtomicReferenceArray>(size); this.gen4Hits = 0L; this.gen3Hits = 0L; this.gen2Hits = 0L; this.gen1Hits = 0L; this.misses = 0L; this.evicts = 0L; } protected void evict(K key, V value) { // hook } public V get(K key) { final AtomicReferenceArray> buckets = this.buckets; if (buckets.length() == 0) { return null; } HashGenMapBucket oldBucket; HashGenMapBucket newBucket; V cacheVal; final int index = Math.abs(key.hashCode()) % buckets.length(); do { oldBucket = buckets.get(index); if (oldBucket == null) { newBucket = null; cacheVal = null; } else { if (oldBucket.gen4Key != null && key.equals(oldBucket.gen4Key)) { HashGenMap.GEN4_HITS.incrementAndGet(this); HashGenMapBucket.GEN4_WEIGHT.incrementAndGet(oldBucket); newBucket = oldBucket; cacheVal = oldBucket.gen4Val; } else if (oldBucket.gen3Key != null && key.equals(oldBucket.gen3Key)) { HashGenMap.GEN3_HITS.incrementAndGet(this); if (HashGenMapBucket.GEN3_WEIGHT.incrementAndGet(oldBucket) > oldBucket.gen4Weight) { newBucket = new HashGenMapBucket(oldBucket.gen3Key, oldBucket.gen3Val, oldBucket.gen3Weight, oldBucket.gen4Key, oldBucket.gen4Val, oldBucket.gen4Weight, oldBucket.gen2Key, oldBucket.gen2Val, oldBucket.gen2Weight, oldBucket.gen1Key, oldBucket.gen1Val, oldBucket.gen1Weight); } else { newBucket = oldBucket; } cacheVal = oldBucket.gen3Val; } else if (oldBucket.gen2Key != null && key.equals(oldBucket.gen2Key)) { HashGenMap.GEN2_HITS.incrementAndGet(this); if (HashGenMapBucket.GEN2_WEIGHT.incrementAndGet(oldBucket) > oldBucket.gen3Weight) { newBucket = new HashGenMapBucket(oldBucket.gen4Key, oldBucket.gen4Val, oldBucket.gen4Weight, oldBucket.gen2Key, oldBucket.gen2Val, oldBucket.gen2Weight, oldBucket.gen3Key, oldBucket.gen3Val, oldBucket.gen3Weight, oldBucket.gen1Key, oldBucket.gen1Val, oldBucket.gen1Weight); } else { newBucket = oldBucket; } cacheVal = oldBucket.gen2Val; } else if (oldBucket.gen1Key != null && key.equals(oldBucket.gen1Key)) { HashGenMap.GEN1_HITS.incrementAndGet(this); if (HashGenMapBucket.GEN1_WEIGHT.incrementAndGet(oldBucket) > oldBucket.gen2Weight) { newBucket = new HashGenMapBucket(oldBucket.gen4Key, oldBucket.gen4Val, oldBucket.gen4Weight, oldBucket.gen3Key, oldBucket.gen3Val, oldBucket.gen3Weight, oldBucket.gen1Key, oldBucket.gen1Val, oldBucket.gen1Weight, oldBucket.gen2Key, oldBucket.gen2Val, oldBucket.gen2Weight); } else { newBucket = oldBucket; } cacheVal = oldBucket.gen1Val; } else { HashGenMap.MISSES.incrementAndGet(this); newBucket = oldBucket; cacheVal = null; } } } while (oldBucket != newBucket && !buckets.compareAndSet(index, oldBucket, newBucket)); return cacheVal; } public V put(K key, V value) { final AtomicReferenceArray> buckets = this.buckets; if (buckets.length() == 0) { return value; } HashGenMapBucket oldBucket; HashGenMapBucket newBucket; K evictKey = null; V evictVal = null; V cacheVal; final int index = Math.abs(key.hashCode()) % buckets.length(); do { oldBucket = buckets.get(index); if (oldBucket == null) { newBucket = new HashGenMapBucket(key, value); cacheVal = value; } else { if (oldBucket.gen4Key != null && key.equals(oldBucket.gen4Key)) { HashGenMap.GEN4_HITS.incrementAndGet(this); HashGenMapBucket.GEN4_WEIGHT.incrementAndGet(oldBucket); newBucket = oldBucket; cacheVal = oldBucket.gen4Val; } else if (oldBucket.gen3Key != null && key.equals(oldBucket.gen3Key)) { HashGenMap.GEN3_HITS.incrementAndGet(this); if (HashGenMapBucket.GEN3_WEIGHT.incrementAndGet(oldBucket) > oldBucket.gen4Weight) { newBucket = new HashGenMapBucket(oldBucket.gen3Key, oldBucket.gen3Val, oldBucket.gen3Weight, oldBucket.gen4Key, oldBucket.gen4Val, oldBucket.gen4Weight, oldBucket.gen2Key, oldBucket.gen2Val, oldBucket.gen2Weight, oldBucket.gen1Key, oldBucket.gen1Val, oldBucket.gen1Weight); } else { newBucket = oldBucket; } cacheVal = oldBucket.gen3Val; } else if (oldBucket.gen2Key != null && key.equals(oldBucket.gen2Key)) { HashGenMap.GEN2_HITS.incrementAndGet(this); if (HashGenMapBucket.GEN2_WEIGHT.incrementAndGet(oldBucket) > oldBucket.gen3Weight) { newBucket = new HashGenMapBucket(oldBucket.gen4Key, oldBucket.gen4Val, oldBucket.gen4Weight, oldBucket.gen2Key, oldBucket.gen2Val, oldBucket.gen2Weight, oldBucket.gen3Key, oldBucket.gen3Val, oldBucket.gen3Weight, oldBucket.gen1Key, oldBucket.gen1Val, oldBucket.gen1Weight); } else { newBucket = oldBucket; } cacheVal = oldBucket.gen2Val; } else if (oldBucket.gen1Key != null && key.equals(oldBucket.gen1Key)) { HashGenMap.GEN1_HITS.incrementAndGet(this); if (HashGenMapBucket.GEN1_WEIGHT.incrementAndGet(oldBucket) > oldBucket.gen2Weight) { newBucket = new HashGenMapBucket(oldBucket.gen4Key, oldBucket.gen4Val, oldBucket.gen4Weight, oldBucket.gen3Key, oldBucket.gen3Val, oldBucket.gen3Weight, oldBucket.gen1Key, oldBucket.gen1Val, oldBucket.gen1Weight, oldBucket.gen2Key, oldBucket.gen2Val, oldBucket.gen2Weight); } else { newBucket = oldBucket; } cacheVal = oldBucket.gen1Val; } else { HashGenMap.MISSES.incrementAndGet(this); evictKey = oldBucket.gen2Key; evictVal = oldBucket.gen2Val; // 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. newBucket = new HashGenMapBucket(oldBucket.gen4Key, oldBucket.gen4Val, oldBucket.gen4Weight - 1, oldBucket.gen3Key, oldBucket.gen3Val, oldBucket.gen3Weight - 1, oldBucket.gen1Key, oldBucket.gen1Val, oldBucket.gen1Weight, key, value, 1); cacheVal = value; } } } while (oldBucket != newBucket && !buckets.compareAndSet(index, oldBucket, newBucket)); if (evictKey != null) { HashGenMap.EVICTS.incrementAndGet(this); this.evict(evictKey, evictVal); } return cacheVal; } public V remove(K key) { final AtomicReferenceArray> buckets = this.buckets; if (buckets.length() == 0) { return null; } HashGenMapBucket oldBucket; HashGenMapBucket newBucket; V cacheVal; final int index = Math.abs(key.hashCode()) % buckets.length(); do { oldBucket = buckets.get(index); if (oldBucket == null) { cacheVal = null; newBucket = null; } else { if (oldBucket.gen4Key != null && key.equals(oldBucket.gen4Key)) { cacheVal = oldBucket.gen4Val; newBucket = new HashGenMapBucket(oldBucket.gen3Key, oldBucket.gen3Val, oldBucket.gen3Weight, oldBucket.gen2Key, oldBucket.gen2Val, oldBucket.gen2Weight, oldBucket.gen1Key, oldBucket.gen1Val, oldBucket.gen1Weight, null, null, 0); } else if (oldBucket.gen3Key != null && key.equals(oldBucket.gen3Key)) { cacheVal = oldBucket.gen3Val; newBucket = new HashGenMapBucket(oldBucket.gen4Key, oldBucket.gen4Val, oldBucket.gen4Weight, oldBucket.gen2Key, oldBucket.gen2Val, oldBucket.gen2Weight, oldBucket.gen1Key, oldBucket.gen1Val, oldBucket.gen1Weight, null, null, 0); } else if (oldBucket.gen2Key != null && key.equals(oldBucket.gen2Key)) { cacheVal = oldBucket.gen2Val; newBucket = new HashGenMapBucket(oldBucket.gen4Key, oldBucket.gen4Val, oldBucket.gen4Weight, oldBucket.gen3Key, oldBucket.gen3Val, oldBucket.gen3Weight, oldBucket.gen1Key, oldBucket.gen1Val, oldBucket.gen1Weight, null, null, 0); } else if (oldBucket.gen1Key != null && key.equals(oldBucket.gen1Key)) { cacheVal = oldBucket.gen1Val; newBucket = new HashGenMapBucket(oldBucket.gen4Key, oldBucket.gen4Val, oldBucket.gen4Weight, oldBucket.gen3Key, oldBucket.gen3Val, oldBucket.gen3Weight, oldBucket.gen2Key, oldBucket.gen2Val, oldBucket.gen2Weight, null, null, 0); } else { cacheVal = null; newBucket = oldBucket; } } } while (oldBucket != newBucket && !buckets.compareAndSet(index, oldBucket, newBucket)); return cacheVal; } public void clear() { final AtomicReferenceArray> buckets = this.buckets; for (int i = 0; i < buckets.length(); i += 1) { buckets.set(i, null); } } public double hitRatio() { final double hits = (double) HashGenMap.GEN4_HITS.get(this) + (double) HashGenMap.GEN3_HITS.get(this) + (double) HashGenMap.GEN2_HITS.get(this) + (double) HashGenMap.GEN1_HITS.get(this); return hits / (hits + (double) HashGenMap.MISSES.get(this)); } @SuppressWarnings("unchecked") static final AtomicLongFieldUpdater> GEN4_HITS = AtomicLongFieldUpdater.newUpdater((Class>) (Class) HashGenMap.class, "gen4Hits"); @SuppressWarnings("unchecked") static final AtomicLongFieldUpdater> GEN3_HITS = AtomicLongFieldUpdater.newUpdater((Class>) (Class) HashGenMap.class, "gen3Hits"); @SuppressWarnings("unchecked") static final AtomicLongFieldUpdater> GEN2_HITS = AtomicLongFieldUpdater.newUpdater((Class>) (Class) HashGenMap.class, "gen2Hits"); @SuppressWarnings("unchecked") static final AtomicLongFieldUpdater> GEN1_HITS = AtomicLongFieldUpdater.newUpdater((Class>) (Class) HashGenMap.class, "gen1Hits"); @SuppressWarnings("unchecked") static final AtomicLongFieldUpdater> MISSES = AtomicLongFieldUpdater.newUpdater((Class>) (Class) HashGenMap.class, "misses"); @SuppressWarnings("unchecked") static final AtomicLongFieldUpdater> EVICTS = AtomicLongFieldUpdater.newUpdater((Class>) (Class) HashGenMap.class, "evicts"); } final class HashGenMapBucket { final K gen4Key; final V gen4Val; final K gen3Key; final V gen3Val; final K gen2Key; final V gen2Val; final K gen1Key; final V gen1Val; volatile int gen4Weight; volatile int gen3Weight; volatile int gen2Weight; volatile int gen1Weight; HashGenMapBucket(K gen4Key, V gen4Val, int gen4Weight, K gen3Key, V gen3Val, int gen3Weight, K gen2Key, V gen2Val, int gen2Weight, K gen1Key, V gen1Val, int gen1Weight) { this.gen4Key = gen4Key; this.gen4Val = gen4Val; this.gen4Weight = gen4Weight; this.gen3Key = gen3Key; this.gen3Val = gen3Val; this.gen3Weight = gen3Weight; this.gen2Key = gen2Key; this.gen2Val = gen2Val; this.gen2Weight = gen2Weight; this.gen1Key = gen1Key; this.gen1Val = gen1Val; this.gen1Weight = gen1Weight; } HashGenMapBucket(K key, V value) { this(null, null, 0, null, null, 0, null, null, 0, key, value, 1); } HashGenMapBucket() { this(null, null, 0, null, null, 0, null, null, 0, null, null, 0); } @SuppressWarnings("unchecked") static final AtomicIntegerFieldUpdater> GEN4_WEIGHT = AtomicIntegerFieldUpdater.newUpdater((Class>) (Class) HashGenMapBucket.class, "gen4Weight"); @SuppressWarnings("unchecked") static final AtomicIntegerFieldUpdater> GEN3_WEIGHT = AtomicIntegerFieldUpdater.newUpdater((Class>) (Class) HashGenMapBucket.class, "gen3Weight"); @SuppressWarnings("unchecked") static final AtomicIntegerFieldUpdater> GEN2_WEIGHT = AtomicIntegerFieldUpdater.newUpdater((Class>) (Class) HashGenMapBucket.class, "gen2Weight"); @SuppressWarnings("unchecked") static final AtomicIntegerFieldUpdater> GEN1_WEIGHT = AtomicIntegerFieldUpdater.newUpdater((Class>) (Class) HashGenMapBucket.class, "gen1Weight"); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy