Skip to content

Commit

Permalink
Support simulations of large caches in weighted policy types
Browse files Browse the repository at this point in the history
In weight-based traces the entries have different sizes, e.g. bytes. The
policies that support this characteristic will evict multiple entries
until at its maximum-size contraint. This was an integer setting which
is too small for the Tencent Photo trace.

The setting was increased to a long and the weighted traces updated
accordingly. The other traces use a checked cast to an int value as a
minimal migration to minimize the chance of introducing bugs. The
linked policy was fixed to handle the entry's weight changing on a
cache hit.

The AdaptSize simulator compatibility was reduced to only its trace
reader. That simulator treats the same key with different weights as
distinct entries, rather than an update. The workaround of hashing
the key and weight as the entry's key causes noticable workload skew.
Since other simulators (like LHD from the same group) don't make this
odd choice, its now restricted to just their trace reader.
  • Loading branch information
ben-manes committed Aug 18, 2020
1 parent 057a78d commit ecd5e13
Show file tree
Hide file tree
Showing 53 changed files with 178 additions and 121 deletions.
2 changes: 1 addition & 1 deletion gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
ext {
versions = [
akka: '2.6.8',
cache2k: '1.3.2.Alpha',
cache2k: '1.3.3.Alpha',
checkerFramework: '3.6.0',
coherence: '20.06',
collision: '0.3.3',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ public TinyLfuSettings tinyLfu() {
return new TinyLfuSettings();
}

public int maximumSize() {
return config().getInt("maximum-size");
public long maximumSize() {
return config().getLong("maximum-size");
}

public boolean isFiles() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.github.benmanes.caffeine.cache.simulator.BasicSettings;
import com.github.benmanes.caffeine.cache.simulator.admission.Frequency;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

/**
Expand Down Expand Up @@ -45,8 +46,8 @@ public CountMin64TinyLfu(Config config) {
BasicSettings settings = new BasicSettings(config);
sketch = new CountMin64(settings.tinyLfu().countMin64().eps(),
settings.tinyLfu().countMin64().confidence(), settings.randomSeed());
sampleSize = Ints.checkedCast(10 * settings.maximumSize());
conservative = settings.tinyLfu().conservative();
sampleSize = 10 * settings.maximumSize();
}

/** Returns the estimated usage frequency of the item. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.github.benmanes.caffeine.cache.simulator.BasicSettings;
import com.github.benmanes.caffeine.cache.simulator.admission.Frequency;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2IntMap;
Expand All @@ -34,7 +35,8 @@ public final class PerfectFrequency implements Frequency {
private int size;

public PerfectFrequency(Config config) {
sampleSize = 10 * new BasicSettings(config).maximumSize();
BasicSettings settings = new BasicSettings(config);
sampleSize = Ints.checkedCast(10 * settings.maximumSize());
counts = new Long2IntOpenHashMap();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import com.github.benmanes.caffeine.cache.simulator.BasicSettings;
import com.github.benmanes.caffeine.cache.simulator.admission.Frequency;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

/**
Expand All @@ -49,7 +50,7 @@ public final class RandomRemovalFrequencyTable implements Frequency {

public RandomRemovalFrequencyTable(Config config) {
BasicSettings settings = new BasicSettings(config);
maxSum = sampleFactor * settings.maximumSize();
maxSum = Ints.checkedCast(sampleFactor * settings.maximumSize());
random = new Random(settings.randomSeed());
table = new HashMap<>(maxSum);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.github.benmanes.caffeine.cache.simulator.BasicSettings;
import com.github.benmanes.caffeine.cache.simulator.admission.Frequency;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

/**
Expand All @@ -37,7 +38,8 @@ public final class TinyCacheAdapter implements Frequency {
*/
public TinyCacheAdapter(Config config) {
BasicSettings settings = new BasicSettings(config);
int nrSets = sampleFactor * settings.maximumSize() / 64; // number of (independent sets)
// number of (independent sets)
int nrSets = Ints.checkedCast(sampleFactor * settings.maximumSize() / 64);
tcs = new TinyCacheSketch(nrSets, 64,settings.randomSeed());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@
import com.github.benmanes.caffeine.cache.simulator.policy.AccessEvent;
import com.github.benmanes.caffeine.cache.simulator.policy.Policy.Characteristic;
import com.google.common.collect.Sets;
import com.google.common.hash.Hashing;

/**
* A reader for the trace files provided by the authors of the AdaptSize algorithm. See
* <a href="https://github.com/dasebe/webcachesim#how-to-get-traces">traces</a>.
* <p>
* The AdaptSize simulator treats identical keys with different weights as unique entries, rather
* than as an update to that entry's size. This behavior is emulated by a key hash.
*
* @author [email protected] (Ben Manes)
*/
Expand All @@ -47,7 +51,12 @@ public Set<Characteristic> characteristics() {
public Stream<AccessEvent> events() throws IOException {
return lines()
.map(line -> line.split(" ", 3))
.map(array -> AccessEvent.forKeyAndWeight(
Long.parseLong(array[1]), Integer.parseInt(array[2])));
.map(array -> {
long key = Long.parseLong(array[1]);
int weight = Integer.parseInt(array[2]);
long hashKey = Hashing.murmur3_128().newHasher()
.putLong(key).putInt(weight).hash().asLong();
return AccessEvent.forKeyAndWeight(hashKey, weight);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ private static final class WeightedAccessEvent extends AccessEvent {
private final int weight;

WeightedAccessEvent(long key, int weight) {
super(cantorHashCode(key, weight));
super(key);
this.weight = weight;
checkArgument(weight >= 0);
}
Expand All @@ -112,11 +112,6 @@ public int weight() {
}
}

/** Cantor pairing function. */
private static long cantorHashCode(long key, int weight) {
return (key + weight) * (key + weight + 1) / 2 + weight;
}

private static final class PenaltiesAccessEvent extends AccessEvent {
private final double missPenalty;
private final double hitPenalty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
Expand Down Expand Up @@ -75,8 +76,8 @@ public final class ArcPolicy implements KeyOnlyPolicy {

public ArcPolicy(Config config) {
BasicSettings settings = new BasicSettings(config);
this.maximumSize = Ints.checkedCast(settings.maximumSize());
this.policyStats = new PolicyStats("adaptive.Arc");
this.maximumSize = settings.maximumSize();
this.data = new Long2ObjectOpenHashMap<>();
this.headT1 = new Node();
this.headT2 = new Node();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
Expand Down Expand Up @@ -61,9 +62,9 @@ public final class CarPolicy implements KeyOnlyPolicy {

public CarPolicy(Config config) {
BasicSettings settings = new BasicSettings(config);
this.maximumSize = Ints.checkedCast(settings.maximumSize());
this.policyStats = new PolicyStats("adaptive.Car");
this.data = new Long2ObjectOpenHashMap<>();
this.maximumSize = settings.maximumSize();
this.headT1 = new Node();
this.headT2 = new Node();
this.headB1 = new Node();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
Expand Down Expand Up @@ -65,9 +66,9 @@ public final class CartPolicy implements KeyOnlyPolicy {

public CartPolicy(Config config) {
BasicSettings settings = new BasicSettings(config);
this.maximumSize = Ints.checkedCast(settings.maximumSize());
this.policyStats = new PolicyStats("adaptive.Cart");
this.data = new Long2ObjectOpenHashMap<>();
this.maximumSize = settings.maximumSize();
this.headT1 = new Node();
this.headT2 = new Node();
this.headB1 = new Node();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.github.benmanes.caffeine.cache.simulator.policy.Policy.KeyOnlyPolicy;
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
Expand Down Expand Up @@ -86,10 +87,10 @@ public final class ClockProPolicy implements KeyOnlyPolicy {

public ClockProPolicy(Config config) {
BasicSettings settings = new BasicSettings(config);
maximumSize = Ints.checkedCast(settings.maximumSize());
policyStats = new PolicyStats("irr.ClockPro");
maximumColdSize = settings.maximumSize();
data = new Long2ObjectOpenHashMap<>();
maximumSize = settings.maximumSize();
maximumColdSize = maximumSize;

// All the hands move in the clockwise direction
handHot = handCold = handTest = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.github.benmanes.caffeine.cache.simulator.policy.Policy.KeyOnlyPolicy;
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
import com.google.common.base.MoreObjects;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
Expand Down Expand Up @@ -66,9 +67,9 @@ public final class DClockPolicy implements KeyOnlyPolicy {

public DClockPolicy(DClockSettings settings, double percentActive) {
this.policyStats = new PolicyStats("irr.DClock (active: %d%%)", (int) (100 * percentActive));
this.maxActive = (int) (percentActive * settings.maximumSize());
this.maximumSize = Ints.checkedCast(settings.maximumSize());
this.maxActive = (int) (percentActive * maximumSize);
this.data = new Long2ObjectOpenHashMap<>();
this.maximumSize = settings.maximumSize();
this.headNonResident = new Node();
this.headInactive = new Node();
this.headActive = new Node();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
Expand Down Expand Up @@ -62,11 +63,11 @@ public final class FrdPolicy implements KeyOnlyPolicy {

public FrdPolicy(Config config) {
FrdSettings settings = new FrdSettings(config);
this.maximumMainResidentSize = (int) (settings.maximumSize() * settings.percentMain());
this.maximumFilterSize = settings.maximumSize() - maximumMainResidentSize;
this.maximumSize = Ints.checkedCast(settings.maximumSize());
this.maximumMainResidentSize = (int) (maximumSize * settings.percentMain());
this.maximumFilterSize = maximumSize - maximumMainResidentSize;
this.policyStats = new PolicyStats("irr.Frd");
this.data = new Long2ObjectOpenHashMap<>();
this.maximumSize = settings.maximumSize();
this.headFilter = new Node();
this.headMain = new Node();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
Expand Down Expand Up @@ -60,16 +61,16 @@ public final class HillClimberFrdPolicy implements KeyOnlyPolicy {

public HillClimberFrdPolicy(Config config) {
FrdSettings settings = new FrdSettings(config);
this.maximumMainResidentSize = (int) (settings.maximumSize() * settings.percentMain());
this.maximumFilterSize = settings.maximumSize() - maximumMainResidentSize;
this.maximumSize = Ints.checkedCast(settings.maximumSize());
this.maximumMainResidentSize = (int) (maximumSize * settings.percentMain());
this.maximumFilterSize = maximumSize - maximumMainResidentSize;
this.policyStats = new PolicyStats("irr.AdaptiveFrd");
this.data = new Long2ObjectOpenHashMap<>();
this.maximumSize = settings.maximumSize();
this.headFilter = new Node();
this.headMain = new Node();

this.sampleSize = 10 * settings.maximumSize();
this.pivot = (int) (0.05 * settings.maximumSize());
this.pivot = (int) (0.05 * maximumSize);
this.sampleSize = 10 * maximumSize;
this.tolerance = 100d * 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.github.benmanes.caffeine.cache.simulator.policy.sketch.Indicator;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
Expand Down Expand Up @@ -54,11 +55,11 @@ public final class IndicatorFrdPolicy implements KeyOnlyPolicy {
public IndicatorFrdPolicy(Config config) {
FrdSettings settings = new FrdSettings(config);
this.period = settings.period();
this.maximumMainResidentSize = (int) (settings.maximumSize() * settings.percentMain());
this.maximumFilterSize = settings.maximumSize() - maximumMainResidentSize;
this.maximumSize = Ints.checkedCast(settings.maximumSize());
this.maximumMainResidentSize = (int) (maximumSize * settings.percentMain());
this.maximumFilterSize = maximumSize - maximumMainResidentSize;
this.policyStats = new PolicyStats("irr.AdaptiveFrd");
this.data = new Long2ObjectOpenHashMap<>();
this.maximumSize = settings.maximumSize();
this.headFilter = new Node();
this.headMain = new Node();
this.indicator = new Indicator(config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
Expand Down Expand Up @@ -78,11 +79,11 @@ public final class LirsPolicy implements KeyOnlyPolicy {

public LirsPolicy(Config config) {
LirsSettings settings = new LirsSettings(config);
this.maximumNonResidentSize = (int) (settings.maximumSize() * settings.nonResidentMultiplier());
this.maximumHotSize = (int) (settings.maximumSize() * settings.percentHot());
this.maximumSize = Ints.checkedCast(settings.maximumSize());
this.maximumNonResidentSize = (int) (maximumSize * settings.nonResidentMultiplier());
this.maximumHotSize = (int) (maximumSize * settings.percentHot());
this.policyStats = new PolicyStats("irr.Lirs");
this.data = new Long2ObjectOpenHashMap<>();
this.maximumSize = settings.maximumSize();
this.evicted = new ArrayList<>();
this.headNR = new Node();
this.headS = new Node();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.github.benmanes.caffeine.cache.simulator.policy.Policy.KeyOnlyPolicy;
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
import com.google.common.base.MoreObjects;
import com.google.common.primitives.Ints;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
Expand All @@ -50,11 +51,11 @@ public final class FrequentlyUsedPolicy implements KeyOnlyPolicy {
final int maximumSize;

public FrequentlyUsedPolicy(Admission admission, EvictionPolicy policy, Config config) {
BasicSettings settings = new BasicSettings(config);
this.policyStats = new PolicyStats(admission.format("linked." + policy.label()));
this.maximumSize = Ints.checkedCast(settings.maximumSize());
this.admittor = admission.from(config, policyStats);
BasicSettings settings = new BasicSettings(config);
this.data = new Long2ObjectOpenHashMap<>();
this.maximumSize = settings.maximumSize();
this.policy = requireNonNull(policy);
this.freq0 = new FrequencyNode();
}
Expand Down
Loading

6 comments on commit ecd5e13

@ohadeytan
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the large caches compatibility.

I'm not sure it's such an odd choice to rehash the key with the weight. I find it very reasonable.
If one asks for an item with a different weight it is other item, even if it has the same key. Perhaps there was a write in between, but since the simulator does not handle writes, I think we could not consider it as a hit that we can serve from the cache (as we do now).

@ben-manes
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it should be a flag then? It certainly doesn't seem consistent across simulators and I agree it's awkward either way. I'd be fine with whatever changes you want here.

@ohadeytan
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A flag seems like a good solution.

BTW, the TencentPhoto trace has a lot of such entries (~5 out of ~20 million that I checked). It makes a big difference to the measured hit-ratio (even for LRU with different simulators).

@ben-manes
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I suppose the alternative is that every weighted policy should validate and replace? That might be a better option since the flag will be weird either way (e.g. why hold onto the old entry with the different weight?) and simulate it as a cache miss?

@ohadeytan
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, maybe it should. My current workaround is to rewrite the trace with the new hashing but evicting the old key could improve the performance.

@ben-manes
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright, either open an issue and I'll try to get to it, or send a PR 😄

Please sign in to comment.