/*
* Copyright 2017 Gilga Einziger. 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.admission.countmin4;
import javax.annotation.Nonnegative;
import com.github.benmanes.caffeine.cache.simulator.BasicSettings;
import com.github.benmanes.caffeine.cache.simulator.BasicSettings.TinyLfuSettings.DoorkeeperSettings;
import com.github.benmanes.caffeine.cache.simulator.membership.FilterType;
import com.github.benmanes.caffeine.cache.simulator.membership.Membership;
import com.typesafe.config.Config;
/**
* A sketch where the aging process is a dynamic process and adjusts to the recency/frequency bias
* of the actual workload.
*
* @author gilga1983@gmail.com (Gil Einziger)
* @author ben.manes@gmail.com (Ben Manes)
*/
public final class AdaptiveResetCountMin4 extends CountMin4 {
static final long ONE_MASK = 0x1111111111111111L;
final Membership doorkeeper;
int additions;
int period;
int prevMisses; // misses in previous interval
int misses; // misses in this interval
int direction = 1; // are we increasing the 'step size' or decreasing it
int eventsToCount; // events yet to count before we make a decision.
public AdaptiveResetCountMin4(Config config) {
super(config);
BasicSettings settings = new BasicSettings(config);
DoorkeeperSettings doorkeeperSettings = settings.tinyLfu().countMin4().periodic().doorkeeper();
if (doorkeeperSettings.enabled()) {
FilterType filterType = settings.membershipFilter();
double expectedInsertionsMultiplier = doorkeeperSettings.expectedInsertionsMultiplier();
long expectedInsertions = (long) expectedInsertionsMultiplier * settings.maximumSize();
doorkeeper = filterType.create(expectedInsertions, doorkeeperSettings.fpp(), config);
} else {
doorkeeper = Membership.disabled();
}
}
@Override
protected void ensureCapacity(@Nonnegative long maximumSize) {
super.ensureCapacity(maximumSize);
period = (maximumSize == 0) ? 10 : (10 * table.length);
if (period <= 0) {
period = Integer.MAX_VALUE;
}
eventsToCount = period;
}
@Override
public int frequency(long e) {
int count = super.frequency(e);
if (doorkeeper.mightContain(e)) {
count++;
}
return count;
}
@Override
public void increment(long e) {
eventsToCount--;
if (!doorkeeper.put(e)) {
super.increment(e);
}
}
/**
* Reduces every counter by half of its original value. To reduce the truncation error, the sample
* is reduced by the number of counters with an odd value.
*/
@Override
protected void tryReset(boolean added) {
additions += step;
if (!added) {
return;
}
if (additions < period) {
return;
}
int count = 0;
for (int i = 0; i < table.length; i++) {
count += Long.bitCount(table[i] & ONE_MASK);
table[i] = (table[i] >>> 1) & RESET_MASK;
}
additions = (additions >>> 1) - (count >>> 2);
doorkeeper.clear();
}
@Override
public void reportMiss() {
// Each time there is a miss, TinyLFU invokes the reportMiss function and we can make decisions
misses++;
if (eventsToCount <= 0) {
eventsToCount = period;
// If this configuration is worse than the previous one, switch directions
if (misses > (prevMisses)) {
direction = -1 * direction;
}
step -= direction;
prevMisses = misses;
misses = 0;
if (step < 1) {
step = 1;
} else if (step > 15) {
step = 15;
}
}
}
}