diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 929beb160c..491aaca290 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -82,7 +82,7 @@ snakeyaml = "2.2" sonarqube = "4.4.1.3373" spotbugs-contrib = "7.6.4" spotbugs-core = "4.8.3" -spotbugs-plugin = "6.0.4" +spotbugs-plugin = "6.0.6" stream = "2.9.8" tcache = "2.0.1" testng = "7.9.0" diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/BasicSettings.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/BasicSettings.java index 6091922264..c300e38488 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/BasicSettings.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/BasicSettings.java @@ -168,6 +168,9 @@ public String sketch() { public boolean conservative() { return config().getBoolean("tiny-lfu.count-min.conservative"); } + public JitterSettings jitter() { + return new JitterSettings(); + } public CountMin4Settings countMin4() { return new CountMin4Settings(); } @@ -175,6 +178,17 @@ public CountMin64Settings countMin64() { return new CountMin64Settings(); } + public final class JitterSettings { + public boolean enabled() { + return config().getBoolean("tiny-lfu.jitter.enabled"); + } + public int threshold() { + return config().getInt("tiny-lfu.jitter.threshold"); + } + public double probability() { + return config().getDouble("tiny-lfu.jitter.probability"); + } + } public final class CountMin4Settings { public String reset() { return config().getString("tiny-lfu.count-min-4.reset"); diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/TinyLfu.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/TinyLfu.java index 439766ba43..6d0c62f5eb 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/TinyLfu.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/TinyLfu.java @@ -15,6 +15,8 @@ */ package com.github.benmanes.caffeine.cache.simulator.admission; +import java.util.Random; + import com.github.benmanes.caffeine.cache.simulator.BasicSettings; import com.github.benmanes.caffeine.cache.simulator.admission.Admittor.KeyOnlyAdmittor; import com.github.benmanes.caffeine.cache.simulator.admission.countmin4.ClimberResetCountMin4; @@ -36,34 +38,48 @@ public final class TinyLfu implements KeyOnlyAdmittor { private final PolicyStats policyStats; private final Frequency sketch; + private final Random random; + + private final double probability; + private final int threshold; public TinyLfu(Config config, PolicyStats policyStats) { + var settings = new BasicSettings(config); + this.random = new Random(settings.randomSeed()); + this.sketch = makeSketch(settings); this.policyStats = policyStats; - this.sketch = makeSketch(config); + if (settings.tinyLfu().jitter().enabled()) { + this.threshold = settings.tinyLfu().jitter().threshold(); + this.probability = settings.tinyLfu().jitter().probability(); + } else { + this.threshold = Integer.MAX_VALUE; + this.probability = 1.0; + } } - private Frequency makeSketch(Config config) { - BasicSettings settings = new BasicSettings(config); + private Frequency makeSketch(BasicSettings settings) { String type = settings.tinyLfu().sketch(); if (type.equalsIgnoreCase("count-min-4")) { String reset = settings.tinyLfu().countMin4().reset(); if (reset.equalsIgnoreCase("periodic")) { - return new PeriodicResetCountMin4(config); + return new PeriodicResetCountMin4(settings.config()); } else if (reset.equalsIgnoreCase("incremental")) { - return new IncrementalResetCountMin4(config); + return new IncrementalResetCountMin4(settings.config()); } else if (reset.equalsIgnoreCase("climber")) { - return new ClimberResetCountMin4(config); + return new ClimberResetCountMin4(settings.config()); } else if (reset.equalsIgnoreCase("indicator")) { - return new IndicatorResetCountMin4(config); + return new IndicatorResetCountMin4(settings.config()); + } else { + throw new IllegalStateException("Unknown reset type: " + reset); } } else if (type.equalsIgnoreCase("count-min-64")) { - return new CountMin64TinyLfu(config); + return new CountMin64TinyLfu(settings.config()); } else if (type.equalsIgnoreCase("random-table")) { - return new RandomRemovalFrequencyTable(config); + return new RandomRemovalFrequencyTable(settings.config()); } else if (type.equalsIgnoreCase("tiny-table")) { - return new TinyCacheAdapter(config); + return new TinyCacheAdapter(settings.config()); } else if (type.equalsIgnoreCase("perfect-table")) { - return new PerfectFrequency(config); + return new PerfectFrequency(settings.config()); } throw new IllegalStateException("Unknown sketch type: " + type); } @@ -81,9 +97,10 @@ public void record(long key) { public boolean admit(long candidateKey, long victimKey) { sketch.reportMiss(); - long candidateFreq = sketch.frequency(candidateKey); - long victimFreq = sketch.frequency(victimKey); - if (candidateFreq > victimFreq) { + int victimFreq = sketch.frequency(victimKey); + int candidateFreq = sketch.frequency(candidateKey); + if ((candidateFreq > victimFreq) + || ((candidateFreq >= threshold) && (random.nextFloat() < probability))) { policyStats.recordAdmission(); return true; } diff --git a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/perfect/PerfectFrequency.java b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/perfect/PerfectFrequency.java index 58dd6d5461..50f661b430 100644 --- a/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/perfect/PerfectFrequency.java +++ b/simulator/src/main/java/com/github/benmanes/caffeine/cache/simulator/admission/perfect/PerfectFrequency.java @@ -55,8 +55,14 @@ public void increment(long e) { } private void reset() { - for (Long2IntMap.Entry entry : counts.long2IntEntrySet()) { - entry.setValue(entry.getIntValue() / 2); + for (var iterator = counts.long2IntEntrySet().iterator(); iterator.hasNext();) { + var entry = iterator.next(); + int newValue = entry.getIntValue() / 2; + if (newValue == 0) { + iterator.remove(); + } else { + entry.setValue(newValue); + } } size /= 2; } diff --git a/simulator/src/main/resources/reference.conf b/simulator/src/main/resources/reference.conf index 2b236111f4..0a196a4c9a 100644 --- a/simulator/src/main/resources/reference.conf +++ b/simulator/src/main/resources/reference.conf @@ -196,6 +196,15 @@ caffeine.simulator { # If increments are conservative by only updating the minimum counters for CountMin sketches count-min.conservative = false + jitter { + # When enabled an otherwise rejected candidate has a random chance of being admitted + enabled = true + # The threshold frequency of a warm candidate to give it a random admission + threshold = 6 + # The admission probability + probability = 0.0078125 + } + count-min-64 { eps = 0.0001 confidence = 0.99 @@ -203,8 +212,8 @@ caffeine.simulator { count-min-4 { # periodic: Resets by periodically halving all counters - # adaptive: Resets periodically at an adaptive interval # incremental: Resets by halving counters in an incremental sweep + # climber or indicator: Resets periodically at an adaptive interval reset = periodic # The multiple of the maximum size determining the number of counters counters-multiplier = 1.0