From d97adf7d67700f564e95805cd3ffbe4c15bae3c6 Mon Sep 17 00:00:00 2001 From: Chris Dennis Date: Mon, 2 Jun 2025 15:36:59 -0400 Subject: [PATCH] Traversers start at the start and are recycled --- .../impl/internal/store/heap/Backend.java | 3 +- .../impl/internal/store/heap/OnHeapStore.java | 5 +- .../internal/store/heap/SimpleBackend.java | 4 +- .../concurrent/ConcurrentHashMapTest.java | 13 ++- .../concurrent/ConcurrentHashMap.java | 96 ++++++++----------- .../concurrent/EvictingConcurrentMap.java | 3 +- 6 files changed, 52 insertions(+), 72 deletions(-) diff --git a/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/Backend.java b/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/Backend.java index e8dbbe92b0..f2e18dd3b7 100644 --- a/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/Backend.java +++ b/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/Backend.java @@ -25,7 +25,6 @@ import java.util.Comparator; import java.util.Iterator; import java.util.Map; -import java.util.Random; import java.util.function.BiFunction; /** @@ -76,5 +75,5 @@ interface Backend { void updateUsageInBytesIfRequired(long delta); - Map.Entry> getEvictionCandidate(Random random, int size, final Comparator> prioritizer, final EvictionAdvisor> evictionAdvisor); + Map.Entry> getEvictionCandidate(int size, final Comparator> prioritizer, final EvictionAdvisor> evictionAdvisor); } diff --git a/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/OnHeapStore.java b/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/OnHeapStore.java index d4376eafe8..605b8ae571 100644 --- a/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/OnHeapStore.java +++ b/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/OnHeapStore.java @@ -1567,14 +1567,13 @@ protected void enforceCapacity() { */ boolean evict(StoreEventSink eventSink) { evictionObserver.begin(); - Random random = new Random(); @SuppressWarnings("unchecked") - Map.Entry> candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, EVICTION_ADVISOR); + Map.Entry> candidate = map.getEvictionCandidate(SAMPLE_SIZE, EVICTION_PRIORITIZER, EVICTION_ADVISOR); if (candidate == null) { // 2nd attempt without any advisor - candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, noAdvice()); + candidate = map.getEvictionCandidate(SAMPLE_SIZE, EVICTION_PRIORITIZER, noAdvice()); } if (candidate == null) { diff --git a/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/SimpleBackend.java b/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/SimpleBackend.java index f83218300c..c63cac4b30 100644 --- a/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/SimpleBackend.java +++ b/ehcache-impl/src/main/java/org/ehcache/impl/internal/store/heap/SimpleBackend.java @@ -50,8 +50,8 @@ public boolean remove(K key, OnHeapValueHolder value) { } @Override - public Map.Entry> getEvictionCandidate(Random random, int size, final Comparator> prioritizer, final EvictionAdvisor> evictionAdvisor) { - return realMap.getEvictionCandidate(random, size, prioritizer, evictionAdvisor); + public Map.Entry> getEvictionCandidate(int size, final Comparator> prioritizer, final EvictionAdvisor> evictionAdvisor) { + return realMap.getEvictionCandidate(size, prioritizer, evictionAdvisor); } @Override diff --git a/ehcache-impl/src/test/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMapTest.java b/ehcache-impl/src/test/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMapTest.java index 36dc295b3b..f65ee23a7f 100644 --- a/ehcache-impl/src/test/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMapTest.java +++ b/ehcache-impl/src/test/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMapTest.java @@ -22,7 +22,6 @@ import java.util.Collection; import java.util.Map; import java.util.Map.Entry; -import java.util.Random; import static org.ehcache.config.Eviction.noAdvice; import static org.hamcrest.MatcherAssert.assertThat; @@ -118,21 +117,21 @@ public int compareTo(BadHashKey o) { @Test public void testRandomSampleOnEmptyMap() { ConcurrentHashMap map = new ConcurrentHashMap<>(); - assertThat(map.getEvictionCandidate(new Random(), 1, null, noAdvice()), nullValue()); + assertThat(map.getEvictionCandidate(1, null, noAdvice()), nullValue()); } @Test public void testEmptyRandomSample() { ConcurrentHashMap map = new ConcurrentHashMap<>(); map.put("foo", "bar"); - assertThat(map.getEvictionCandidate(new Random(), 0, null, noAdvice()), nullValue()); + assertThat(map.getEvictionCandidate(0, null, noAdvice()), nullValue()); } @Test public void testOversizedRandomSample() { ConcurrentHashMap map = new ConcurrentHashMap<>(); map.put("foo", "bar"); - Entry candidate = map.getEvictionCandidate(new Random(), 2, null, noAdvice()); + Entry candidate = map.getEvictionCandidate(2, null, noAdvice()); assertThat(candidate.getKey(), is("foo")); assertThat(candidate.getValue(), is("bar")); } @@ -143,7 +142,7 @@ public void testUndersizedRandomSample() { for (int i = 0; i < 1000; i++) { map.put(Integer.toString(i), Integer.toString(i)); } - Entry candidate = map.getEvictionCandidate(new Random(), 2, (t, t1) -> 0, noAdvice()); + Entry candidate = map.getEvictionCandidate(2, (t, t1) -> 0, noAdvice()); assertThat(candidate, notNullValue()); } @@ -153,7 +152,7 @@ public void testFullyAdvisedAgainstEvictionRandomSample() { for (int i = 0; i < 1000; i++) { map.put(Integer.toString(i), Integer.toString(i)); } - Entry candidate = map.getEvictionCandidate(new Random(), 2, null, (key, value) -> true); + Entry candidate = map.getEvictionCandidate(2, null, (key, value) -> true); assertThat(candidate, nullValue()); } @@ -163,7 +162,7 @@ public void testSelectivelyAdvisedAgainstEvictionRandomSample() { for (int i = 0; i < 1000; i++) { map.put(Integer.toString(i), Integer.toString(i)); } - Entry candidate = map.getEvictionCandidate(new Random(), 20, (t, t1) -> 0, (key, value) -> key.length() > 1); + Entry candidate = map.getEvictionCandidate(20, (t, t1) -> 0, (key, value) -> key.length() > 1); assertThat(candidate.getKey().length(), is(1)); } diff --git a/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMap.java b/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMap.java index 6bbd5baa59..97ad217521 100644 --- a/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMap.java +++ b/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/ConcurrentHashMap.java @@ -36,6 +36,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.Deque; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; @@ -46,6 +47,7 @@ import java.util.Random; import java.util.Set; import java.util.Spliterator; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.CountedCompleter; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.atomic.AtomicReference; @@ -6478,72 +6480,54 @@ else if (f.hash == MOVED) return invalidated; } - public Entry getEvictionCandidate(Random rndm, int size, Comparator prioritizer, EvictionAdvisor evictionAdvisor) { - Node[] tab = table; - if (tab == null || size == 0) { - return null; - } + private final Deque> evictionTraversers = new ConcurrentLinkedDeque<>(); - K maxKey = null; - V maxValue = null; + public Entry getEvictionCandidate(int size, Comparator prioritizer, EvictionAdvisor evictionAdvisor) { + if (size == 0) { + return null; + } - int n = tab.length; - int start = rndm.nextInt(n); + K maxKey = null; + V maxValue = null; - Traverser t = new Traverser<>(tab, n, start, n); - for (Node p; (p = t.advance()) != null;) { - K key = p.key; - V val = p.val; - if (!evictionAdvisor.adviseAgainstEviction(key, val)) { - if (maxKey == null || prioritizer.compare(val, maxValue) > 0) { - maxKey = key; - maxValue = val; - } - if (--size == 0) { - for (int terminalIndex = t.index; (p = t.advance()) != null && t.index == terminalIndex; ) { - key = p.key; - val = p.val; - if (!evictionAdvisor.adviseAgainstEviction(key, val) && prioritizer.compare(val, maxValue) > 0) { - maxKey = key; - maxValue = val; - } - } - return new MapEntry<>(maxKey, maxValue, this); - } - } + boolean exhaustive = false; + do { + Traverser t = evictionTraversers.poll(); + if (t == null) { + Node[] tab; + int f = (tab = table) == null ? 0 : tab.length; + t = new Traverser<>(tab, f, 0, f); + exhaustive = true; } - return getEvictionCandidateWrap(tab, start, size, maxKey, maxValue, prioritizer, evictionAdvisor); - } - - private Entry getEvictionCandidateWrap(Node[] tab, int start, int size, K maxKey, V maxVal, Comparator prioritizer, EvictionAdvisor evictionAdvisor) { - Traverser t = new Traverser<>(tab, tab.length, 0, start); - for (Node p; (p = t.advance()) != null;) { + boolean exhausted = false; + try { + for (Node p; (p = t.advance()) != null; ) { K key = p.key; V val = p.val; if (!evictionAdvisor.adviseAgainstEviction(key, val)) { - if (maxKey == null || prioritizer.compare(val, maxVal) > 0) { - maxKey = key; - maxVal = val; - } - if (--size == 0) { - for (int terminalIndex = t.index; (p = t.advance()) != null && t.index == terminalIndex; ) { - key = p.key; - val = p.val; - if (!evictionAdvisor.adviseAgainstEviction(key, val) && prioritizer.compare(val, maxVal) > 0) { - maxKey = key; - maxVal = val; - } - } - return new MapEntry<>(maxKey, maxVal, this); - } + if (maxKey == null || prioritizer.compare(val, maxValue) > 0) { + maxKey = key; + maxValue = val; + } + if (--size == 0) { + return new MapEntry<>(maxKey, maxValue, this); + } } + } + exhausted = true; + } finally { + if (!exhausted) { + evictionTraversers.push(t); + } } - if (maxKey == null) { - return null; - } else { - return new MapEntry<>(maxKey, maxVal, this); - } + } while (!exhaustive); + + if (maxKey == null) { + return null; + } else { + return new MapEntry<>(maxKey, maxValue, this); + } } // END OF EHCACHE SPECIFIC } diff --git a/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/EvictingConcurrentMap.java b/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/EvictingConcurrentMap.java index c0872c848e..2587bd9918 100644 --- a/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/EvictingConcurrentMap.java +++ b/ehcache-impl/src/unsafe/java/org/ehcache/impl/internal/concurrent/EvictingConcurrentMap.java @@ -29,13 +29,12 @@ public interface EvictingConcurrentMap extends ConcurrentMap{ /** * Return the preferred entry to evict based on a sample of entries taken from the map. * - * @param rndm Random implementation used to determine the sample randomly * @param size Number of sampled entries * @param prioritizer Prioritizer used to determine the best entry to evict in the sample * @param evictionAdvisor Can veto against the eviction of an entry * @return Entry to evict or null is none was found */ - Entry getEvictionCandidate(Random rndm, int size, Comparator prioritizer, EvictionAdvisor evictionAdvisor); + Entry getEvictionCandidate(int size, Comparator prioritizer, EvictionAdvisor evictionAdvisor); /** * Returns the number of mappings. This method should be used