/* * Copyright 2015 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 java.util.Set; import com.github.benmanes.caffeine.cache.simulator.BasicSettings; import com.github.benmanes.caffeine.cache.simulator.policy.Policy; import com.github.benmanes.caffeine.cache.simulator.policy.PolicyStats; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSet; import com.typesafe.config.Config; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; /** * The 2Q algorithm. This algorithm uses a queue for items that are seen once (IN), a queue for * items seen multiple times (MAIN), and a non-resident queue for evicted items that are being * monitored (OUT). The maximum size of the IN and OUT queues must be tuned with the authors * recommending 20% and 50% of the maximum size, respectively. * <p> * This implementation is based on the pseudo code provided by the authors in their paper * <a href="http://www.vldb.org/conf/1994/P439.PDF">2Q: A Low Overhead High Performance Buffer * Management Replacement Algorithm</a>. For consistency with other policies, this version places * the next item to be removed at the head and most recently added at the tail of the queue. * * @author ben.manes@gmail.com (Ben Manes) */ public final class TwoQueuePolicy implements Policy { static final Node UNLINKED = new Node(); final Long2ObjectMap<Node> data; final PolicyStats policyStats; final int maximumSize; int sizeIn; final int maxIn; final Node headIn; int sizeOut; final int maxOut; final Node headOut; int sizeMain; final Node headMain; public TwoQueuePolicy(Config config) { TwoQueueSettings settings = new TwoQueueSettings(config); this.headIn = new Node(); this.headOut = new Node(); this.headMain = new Node(); this.maximumSize = settings.maximumSize(); this.data = new Long2ObjectOpenHashMap<>(); this.maxIn = (int) (maximumSize * settings.percentIn()); this.policyStats = new PolicyStats("two-queue.TwoQueue"); this.maxOut = (int) (maximumSize * settings.percentOut()); } /** Returns all variations of this policy based on the configuration parameters. */ public static Set<Policy> policies(Config config) { return ImmutableSet.of(new TwoQueuePolicy(config)); } @Override @SuppressWarnings("PMD.ConfusingTernary") public void record(long key) { // On accessing a page X : // if X is in Am then // move X to the head of Am // else if (X is in Alout) then // reclaimfor(X) // add X to the head of Am // else if (X is in Alin) // // do nothing // else // X is in no queue // reclaimfor(X) // add X to the head of Alin // end if policyStats.recordOperation(); Node node = data.get(key); if (node != null) { switch (node.type) { case MAIN: node.moveToTail(headMain); policyStats.recordHit(); return; case OUT: node.remove(); sizeOut--; reclaimfor(node); node.appendToTail(headMain); node.type = QueueType.MAIN; sizeMain++; policyStats.recordMiss(); return; case IN: // do nothing policyStats.recordHit(); return; } } else { node = new Node(key); node.type = QueueType.IN; reclaimfor(node); node.appendToTail(headIn); sizeIn++; policyStats.recordMiss(); } } private void reclaimfor(Node node) { // if there are free page slots then // put X into a free page slot // else if (size(Alin) > Kin) // page out the tail of Alin, call it Y // add identifier of Y to the head of Alout // if (size(Alout) > Kout) // remove identifier of Z from the tail of Alout // end if // put X into the reclaimed page slot // else // page out the tail of Am, call it Y // // do not put it on Alout; it hasn’t been accessed for a while // put X into the reclaimed page slot // end if if ((sizeMain + sizeIn) < maximumSize) { data.put(node.key, node); } else if (sizeIn > maxIn) { // IN is full, move to OUT Node n = headIn.next; n.remove(); sizeIn--; n.appendToTail(headOut); n.type = QueueType.OUT; sizeOut++; if (sizeOut > maxOut) { // OUT is full, drop oldest policyStats.recordEviction(); Node victim = headOut.next; data.remove(victim.key); victim.remove(); sizeOut--; } data.put(node.key, node); } else { // OUT has room, evict from MAIN policyStats.recordEviction(); Node victim = headMain.next; data.remove(victim.key); victim.remove(); sizeMain--; data.put(node.key, node); } } @Override public PolicyStats stats() { return policyStats; } enum QueueType { MAIN, IN, OUT, } static final class Node { final long key; Node prev; Node next; QueueType type; Node() { this.key = Long.MIN_VALUE; this.prev = this; this.next = this; } Node(long key) { this.key = key; this.prev = UNLINKED; this.next = UNLINKED; } /** Appends the node to the tail of the list. */ public void appendToTail(Node head) { 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) { // 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(key != Long.MIN_VALUE); prev.next = next; next.prev = prev; prev = next = UNLINKED; // mark as unlinked } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("key", key) .add("type", type) .toString(); } } static final class TwoQueueSettings extends BasicSettings { public TwoQueueSettings(Config config) { super(config); } public double percentIn() { return config().getDouble("two-queue.percent-in"); } public double percentOut() { return config().getDouble("two-queue.percent-out"); } } }