Skip to content

Commit

Permalink
Add the Quick Demotion - Lazy Promotion algorithm to the simulator
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-manes committed Jun 25, 2023
1 parent b525eb7 commit 6a7a87a
Show file tree
Hide file tree
Showing 4 changed files with 308 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ private abstract class Command implements Runnable {
try {
execute();
} catch (Throwable t) {
t.printStackTrace();
parent.interrupt();
throw t;
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import com.github.benmanes.caffeine.cache.simulator.policy.sketch.tinycache.TinyCachePolicy;
import com.github.benmanes.caffeine.cache.simulator.policy.sketch.tinycache.TinyCacheWithGhostCachePolicy;
import com.github.benmanes.caffeine.cache.simulator.policy.sketch.tinycache.WindowTinyCachePolicy;
import com.github.benmanes.caffeine.cache.simulator.policy.two_queue.QdlpPolicy;
import com.github.benmanes.caffeine.cache.simulator.policy.two_queue.TuQueuePolicy;
import com.github.benmanes.caffeine.cache.simulator.policy.two_queue.TwoQueuePolicy;
import com.google.auto.value.AutoValue;
Expand Down Expand Up @@ -171,6 +172,7 @@ private void registerSampled() {
}

private void registerTwoQueue() {
register(QdlpPolicy.class, QdlpPolicy::new);
register(TuQueuePolicy.class, TuQueuePolicy::new);
register(TwoQueuePolicy.class, TwoQueuePolicy::new);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
/*
* Copyright 2023 Ben Manes. All Rights Reserved.
*
* 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 com.github.benmanes.caffeine.cache.simulator.policy.two_queue;

import static com.google.common.base.Preconditions.checkState;

import com.github.benmanes.caffeine.cache.simulator.BasicSettings;
import com.github.benmanes.caffeine.cache.simulator.policy.Policy.KeyOnlyPolicy;
import com.github.benmanes.caffeine.cache.simulator.policy.Policy.PolicySpec;
import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats;
import com.google.common.base.MoreObjects;
import com.typesafe.config.Config;

import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;

/**
* The Quick Demotion - Lazy Promotion algorithm. This algorithm uses a probationary FIFO queue to
* evaluate whether a recent arrival should be admitted into the main region based on a frequency
* threshold. A rejected candidate is placed into a ghost cache, where a cache miss that hits in the
* ghost cache will cause the entry to be immediately promoted into the main region. This admission
* scheme is referred to as "quick demotion" by the authors. The main region uses an n-bit clock
* eviction policy, which is referred to as "lazy promotion" by the authors.
* <p>
* This implementation is based on the code provided by the authors in their
* <a href="https://github.com/Thesys-lab/HotOS23-QD-LP">repository</a> and described by the paper
* <a href="https://jasony.me/publication/hotos23-qdlp.pdf">FIFO can be Better than LRU: the Power
* of Lazy Promotion and Quick Demotion</a> and the accompanying
* <a href="https://jasony.me/slides/hotos23-qdlp.pdf">slides</a>.
*
* @author ben.manes@gmail.com (Ben Manes)
*/
@PolicySpec(name = "two-queue.Qdlp")
public final class QdlpPolicy implements KeyOnlyPolicy {
final Long2ObjectMap<Node> data;
final PolicyStats policyStats;
final Node headGhost;
final Node headFifo;
final Node headMain;

final int mainMaximumEntryFrequency;
final int maximumSize;
final int maxGhost;
final int maxFifo;

int sizeFifo;
int sizeMain;
int sizeGhost;
int moveToMainThreshold;

public QdlpPolicy(Config config) {
QdlpSettings settings = new QdlpSettings(config);
this.data = new Long2ObjectOpenHashMap<>();
this.policyStats = new PolicyStats(name());
this.headGhost = new Node();
this.headFifo = new Node();
this.headMain = new Node();

this.moveToMainThreshold = settings.moveToMainThreshold();
this.maximumSize = Math.toIntExact(settings.maximumSize());
this.maxFifo = (int) (maximumSize * settings.percentFifo());
this.maxGhost = (int) (maximumSize * settings.percentGhost());
this.mainMaximumEntryFrequency = settings.mainMaximumEntryFrequency();
}

@Override
public void record(long key) {
policyStats.recordOperation();
Node node = data.get(key);
if (node == null) {
onMiss(key);
} else if (node.type == QueueType.GHOST) {
onGhostHit(node);
} else {
onHit(node);
}
}

private void onHit(Node node) {
if (node.type == QueueType.FIFO) {
node.frequency++;
} else if (node.type == QueueType.MAIN) {
node.frequency = Math.min(node.frequency + 1, mainMaximumEntryFrequency);
}
policyStats.recordHit();
}

private void onGhostHit(Node node) {
policyStats.recordMiss();
node.remove();
sizeGhost--;

node.appendToTail(headMain);
node.type = QueueType.MAIN;
sizeMain++;
evict();
}

private void onMiss(long key) {
Node node = new Node(key);
node.appendToTail(headFifo);
node.type = QueueType.FIFO;
policyStats.recordMiss();
data.put(key, node);
sizeFifo++;
evict();

if (sizeFifo > maxFifo) {
Node promoted = headFifo.next;
promoted.remove();
sizeFifo--;

promoted.appendToTail(headMain);
promoted.type = QueueType.MAIN;
sizeMain++;
}
}

private void evict() {
if ((sizeFifo + sizeMain) <= maximumSize) {
return;
}
policyStats.recordEviction();

if ((maxFifo == 0) || (sizeFifo == 0)) {
evictFromMain();
return;
}

Node candidate = headFifo.next;
checkState(data.containsKey(candidate.key), candidate);
int freq = candidate.frequency;
candidate.frequency = 0;
candidate.remove();

if (freq >= moveToMainThreshold) {
evictFromMain();
candidate.appendToTail(headMain);
candidate.type = QueueType.MAIN;
sizeMain++;
sizeFifo--;
checkState(data.size() <= (maximumSize + maxGhost),
"%s > %s", data.size(), (maximumSize + maxGhost));

} else {
candidate.appendToTail(headGhost);
candidate.type = QueueType.GHOST;
candidate.frequency = 0;
sizeGhost++;
sizeFifo--;

if (sizeGhost > maxGhost) {
var ghost = headGhost.next;
checkState(data.remove(ghost.key) != null);
ghost.remove();
sizeGhost--;
}
checkState(data.size() <= (maximumSize + maxGhost),
"%s > %s", data.size(), (maximumSize + maxGhost));
}
}

private void evictFromMain() {
for (;;) {
Node victim = headMain.next;
if (victim.frequency == 0) {
checkState(data.remove(victim.key) != null);
victim.remove();
sizeMain--;
break;
}
victim.frequency--;
victim.moveToTail(headMain);
}
}

@Override
public void finished() {
int maximum = (maximumSize + maxGhost);
checkState(data.size() <= maximum, "%s > %s", data.size(), maximum);

long ghosts = data.values().stream().filter(node -> node.type == QueueType.GHOST).count();
checkState(ghosts == sizeGhost, "ghosts: %s != %s", ghosts, sizeGhost);
checkState(ghosts <= maxGhost, "ghosts: %s > %s", ghosts, maxGhost);
}

@Override
public PolicyStats stats() {
return policyStats;
}

enum QueueType {
FIFO,
MAIN,
GHOST,
}

static final class Node {
final long key;

Node prev;
Node next;
QueueType type;
int frequency;

Node() {
this.key = Long.MIN_VALUE;
this.prev = this;
this.next = this;
}

Node(long key) {
this.key = key;
}

/** Appends the node to the tail of the list. */
public void appendToTail(Node head) {
checkState(prev == null);
checkState(next == null);

Node tail = head.prev;
head.prev = this;
tail.next = this;
next = head;
prev = tail;
}

/** Moves the node to the tail. */
public void moveToTail(Node head) {
checkState(prev != null);
checkState(next != null);

// unlink
prev.next = next;
next.prev = prev;

// link
next = head;
prev = head.prev;
head.prev = this;
prev.next = this;
}

/** Removes the node from the list. */
public void remove() {
checkState(prev != null);
checkState(next != null);

prev.next = next;
next.prev = prev;
prev = next = null;
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("key", key)
.add("type", type)
.toString();
}
}

static final class QdlpSettings extends BasicSettings {
public QdlpSettings(Config config) {
super(config);
}
public double percentFifo() {
return config().getDouble("qdlp.percent-fifo");
}
public double percentGhost() {
return config().getDouble("qdlp.percent-ghost");
}
public int moveToMainThreshold() {
return config().getInt("qdlp.move-to-main-threshold");
}
public int mainMaximumEntryFrequency() {
return config().getInt("qdlp.main-clock-maximum-frequency");
}
}
}
12 changes: 12 additions & 0 deletions simulator/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ caffeine.simulator {
# Policies based on the 2Q algorithm
two-queue.TwoQueue,
two-queue.TuQueue,
two-queue.Qdlp,

# Policies based on a sketch algorithm
sketch.WindowTinyLfu,
Expand Down Expand Up @@ -176,6 +177,17 @@ caffeine.simulator {
percent-warm = 0.33
}

qdlp {
# The percentage for the FIFO queue
percent-fifo = 0.10
# The percentage for the GHOST queue
percent-ghost = 0.90
# The promotion frequency threshold
move-to-main-threshold = 1
# The n-bit clock frequency for the MAIN queue
main-clock-maximum-frequency = 1
}

tiny-lfu {
# CountMinSketch: count-min-4 (4-bit), count-min-64 (64-bit)
# Table: random-table, tiny-table, perfect-table
Expand Down

0 comments on commit 6a7a87a

Please sign in to comment.