/* * Copyright 2016 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.sketch.segment; import static java.util.stream.Collectors.toSet; import java.util.List; import java.util.Random; import java.util.Set; import com.github.benmanes.caffeine.cache.simulator.BasicSettings; import com.github.benmanes.caffeine.cache.simulator.admission.Admittor; import com.github.benmanes.caffeine.cache.simulator.admission.TinyLfu; 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.typesafe.config.Config; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; /** * The Window TinyLfu algorithm where the window and main spaces implements random eviction. * * @author ben.manes@gmail.com (Ben Manes) */ public final class RandomWindowTinyLfuPolicy implements Policy { final Long2ObjectMap<Node> data; final PolicyStats policyStats; final Admittor admittor; final int maximumSize; final Random random; final Node[] window; final Node[] main; int windowSize; int mainSize; public RandomWindowTinyLfuPolicy(double percentMain, RandomWindowTinyLfuSettings settings) { String name = String.format("sketch.RandomWindowTinyLfu (%.0f%%)", 100 * (1.0d - percentMain)); policyStats = new PolicyStats(name); admittor = new TinyLfu(settings.config(), policyStats); random = new Random(settings.randomSeed()); data = new Long2ObjectOpenHashMap<>(); maximumSize = settings.maximumSize(); int maxMain = (int) (maximumSize * percentMain); window = new Node[maximumSize - maxMain + 1]; main = new Node[maxMain + 1]; } /** Returns all variations of this policy based on the configuration parameters. */ public static Set<Policy> policies(Config config) { RandomWindowTinyLfuSettings settings = new RandomWindowTinyLfuSettings(config); return settings.percentMain().stream() .map(percentMain -> new RandomWindowTinyLfuPolicy(percentMain, settings)) .collect(toSet()); } @Override public PolicyStats stats() { return policyStats; } @Override public void record(long key) { Node node = data.get(key); admittor.record(key); if (node == null) { node = new Node(key, windowSize); policyStats.recordOperation(); policyStats.recordMiss(); window[node.index] = node; data.put(key, node); windowSize++; evict(); } else { policyStats.recordOperation(); policyStats.recordHit(); } } /** Evicts if the map exceeds the maximum capacity. */ private void evict() { if (windowSize <= (window.length - 1)) { return; } Node candidate = window[random.nextInt(window.length)]; removeFromTable(window, candidate); windowSize--; main[mainSize] = candidate; candidate.index = mainSize; mainSize++; if (data.size() > maximumSize) { Node victim = main[random.nextInt(main.length)]; Node evict = admittor.admit(candidate.key, victim.key) ? victim : candidate; removeFromTable(main, evict); data.remove(evict.key); mainSize--; policyStats.recordEviction(); } } /** Removes the node from the table and adds the index to the free list. */ private void removeFromTable(Node[] table, Node node) { int last = table.length - 1; table[node.index] = table[last]; table[node.index].index = node.index; table[last] = null; } /** A node on the double-linked list. */ static final class Node { final long key; int index; /** Creates a new node. */ public Node(long key, int index) { this.index = index; this.key = key; } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("key", key) .add("index", index) .toString(); } } static final class RandomWindowTinyLfuSettings extends BasicSettings { public RandomWindowTinyLfuSettings(Config config) { super(config); } public List<Double> percentMain() { return config().getDoubleList("random-window-tiny-lfu.percent-main"); } } }