/* * 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.climbing; import static com.github.benmanes.caffeine.cache.simulator.policy.sketch.climbing.HillClimber.Adaptation.Type.DECREASE_WINDOW; import static com.github.benmanes.caffeine.cache.simulator.policy.sketch.climbing.HillClimber.Adaptation.Type.INCREASE_WINDOW; import java.util.Random; import com.github.benmanes.caffeine.cache.simulator.BasicSettings; import com.typesafe.config.Config; /** * A simulated annealing hill climber. * * @author ben.manes@gmail.com (Ben Manes) */ final class SimulatedAnnealingClimber implements HillClimber { private final Random random; private int pivot; private final int initialPivot; private int sample; private final int sampleSize; private int hitsInSample; private int missesInSample; private double previousHitRate; private boolean increaseWindow; private double temperature = 1.0; private final double coolDownRate; private final double minTemperature; private final double restartTolerance; private final double coolDownTolerance; public SimulatedAnnealingClimber(Config config) { SimulatedAnnealingSettings settings = new SimulatedAnnealingSettings(config); this.random = new Random(settings.randomSeed()); this.initialPivot = (int) (settings.percentPivot() * settings.maximumSize()); this.sampleSize = (int) (settings.percentSample() * settings.maximumSize()); this.pivot = initialPivot; this.coolDownRate = settings.coolDownRate(); this.minTemperature = settings.minTemperature(); this.restartTolerance = 100 * settings.restartTolerance(); this.coolDownTolerance = 100 * settings.coolDownTolerance(); } @Override public void onMiss(long key) { missesInSample++; sample++; } @Override public void onHit(long key, QueueType queueType) { hitsInSample++; sample++; } @Override public Adaptation adapt(int windowSize, int protectedSize) { Adaptation adaption = Adaptation.HOLD; if (sample >= sampleSize) { double hitRate = (100d * hitsInSample) / (hitsInSample + missesInSample); if (!Double.isNaN(hitRate) && !Double.isInfinite(hitRate) && (previousHitRate != 0.0)) { adaption = adjust(hitRate); } previousHitRate = hitRate; missesInSample = 0; hitsInSample = 0; sample = 0; } return adaption; } private Adaptation adjust(double hitRate) { if ((previousHitRate - hitRate) >= restartTolerance) { pivot = initialPivot; temperature = 1.0; } Adaptation.Type adaptionType = Adaptation.Type.HOLD; if (temperature > minTemperature) { double acceptanceProbability = Math.exp((hitRate - previousHitRate) / (100 * temperature)); double criteria = random.nextGaussian(); if ((hitRate < previousHitRate) && acceptanceProbability <= criteria) { increaseWindow = !increaseWindow; pivot--; } if ((previousHitRate - hitRate) > coolDownTolerance) { temperature = coolDownRate * temperature; pivot = 1 + (int) (pivot * temperature); } adaptionType = increaseWindow ? INCREASE_WINDOW : DECREASE_WINDOW; } return new Adaptation(adaptionType, pivot); } static final class SimulatedAnnealingSettings extends BasicSettings { static final String BASE_PATH = "hill-climber-window-tiny-lfu.simulated-annealing."; public SimulatedAnnealingSettings(Config config) { super(config); } public double percentPivot() { return config().getDouble(BASE_PATH + "percent-pivot"); } public double percentSample() { return config().getDouble(BASE_PATH + "percent-sample"); } public double coolDownRate() { return config().getDouble(BASE_PATH + "cool-down-rate"); } public double minTemperature() { return config().getDouble(BASE_PATH + "min-temperature"); } public double restartTolerance() { return config().getDouble(BASE_PATH + "restart-tolerance"); } public double coolDownTolerance() { return config().getDouble(BASE_PATH + "cool-down-tolerance"); } } }