/*
* 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.admission.countmin4;
import static com.google.common.base.Preconditions.checkArgument;
import javax.annotation.Nonnegative;
import com.github.benmanes.caffeine.cache.simulator.BasicSettings;
import com.github.benmanes.caffeine.cache.simulator.admission.Frequency;
import com.typesafe.config.Config;
/**
* A probabilistic multiset for estimating the popularity of an element within a time window. The
* maximum frequency of an element is limited to 15 (4-bits) and extensions provide the aging
* process.
*
* @author ben.manes@gmail.com (Ben Manes)
*/
public abstract class CountMin4 implements Frequency {
static final long[] SEED = new long[] { // A mixture of seeds from FNV-1a, CityHash, and Murmur3
0xc3a5c85c97cb3127L, 0xb492b66fbe98f273L, 0x9ae16a3b2f90404fL, 0xcbf29ce484222325L};
static final long RESET_MASK = 0x7777777777777777L;
protected final boolean conservative;
protected final int randomSeed;
protected int tableMask;
protected long[] table;
protected int step = 1;
/**
* Creates a frequency sketch that can accurately estimate the popularity of elements given
* the maximum size of the cache.
*/
protected CountMin4(Config config) {
BasicSettings settings = new BasicSettings(config);
conservative = settings.tinyLfu().conservative();
checkArgument(settings.randomSeed() != 0);
randomSeed = settings.randomSeed();
double countersMultiplier = settings.tinyLfu().countMin4().countersMultiplier();
long counters = (long) (countersMultiplier * settings.maximumSize());
ensureCapacity(counters);
}
/**
* Increases the capacity of this <tt>FrequencySketch</tt> instance, if necessary, to ensure that
* it can accurately estimate the popularity of elements given the maximum size of the cache. This
* operation forgets all previous counts when resizing.
*
* @param maximumSize the maximum size of the cache
*/
protected void ensureCapacity(@Nonnegative long maximumSize) {
checkArgument(maximumSize >= 0);
int maximum = (int) Math.min(maximumSize, Integer.MAX_VALUE >>> 1);
if ((table != null) && (table.length >= maximum)) {
return;
}
table = new long[(maximum == 0) ? 1 : ceilingNextPowerOfTwo(maximum)];
tableMask = Math.max(0, table.length - 1);
}
/**
* Returns the estimated number of occurrences of an element, up to the maximum (15).
*
* @param e the element to count occurrences of
* @return the estimated number of occurrences of the element; possibly zero but never negative
*/
@Override
@Nonnegative
public int frequency(long e) {
int hash = spread(Long.hashCode(e));
int start = (hash & 3) << 2;
int frequency = Integer.MAX_VALUE;
for (int i = 0; i < 4; i++) {
int index = indexOf(hash, i);
int count = (int) ((table[index] >>> ((start + i) << 2)) & 0xfL);
frequency = Math.min(frequency, count);
}
return frequency;
}
/**
* Increments the popularity of the element if it does not exceed the maximum (15). The popularity
* of all elements will be periodically down sampled when the observed events exceeds a threshold.
* This process provides a frequency aging to allow expired long term entries to fade away.
*
* @param e the element to add
*/
@Override
public void increment(long e) {
if (conservative) {
conservativeIncrement(e);
} else {
regularIncrement(e);
}
}
/** Increments all of the associated counters. */
void regularIncrement(long e) {
int hash = spread(Long.hashCode(e));
int start = (hash & 3) << 2;
// Loop unrolling improves throughput by 5m ops/s
int index0 = indexOf(hash, 0);
int index1 = indexOf(hash, 1);
int index2 = indexOf(hash, 2);
int index3 = indexOf(hash, 3);
boolean added = incrementAt(index0, start, step);
added |= incrementAt(index1, start + 1, step);
added |= incrementAt(index2, start + 2, step);
added |= incrementAt(index3, start + 3, step);
tryReset(added);
}
/** Increments the associated counters that are at the observed minimum. */
void conservativeIncrement(long e) {
int hash = spread(Long.hashCode(e));
int start = (hash & 3) << 2;
int[] index = new int[4];
int[] count = new int[4];
int min = Integer.MAX_VALUE;
for (int i = 0; i < 4; i++) {
index[i] = indexOf(hash, i);
count[i] = (int) ((table[index[i]] >>> ((start + i) << 2)) & 0xfL);
min = Math.min(min, count[i]);
}
if (min == 15) {
tryReset(false);
return;
}
for (int i = 0; i < 4; i++) {
if (count[i] == min) {
incrementAt(index[i], start + i, step);
}
}
tryReset(true);
}
/** Performs the aging process after an addition to allow old entries to fade away. */
protected void tryReset(boolean added) {}
/**
* Increments the specified counter by 1 if it is not already at the maximum value (15).
*
* @param i the table index (16 counters)
* @param j the counter to increment
* @param step the increase amount
* @return if incremented
*/
boolean incrementAt(int i, int j, long step) {
int offset = j << 2;
long mask = (0xfL << offset);
if ((table[i] & mask) != mask) {
long current = (table[i] & mask) >>> offset;
long update = Math.min(current + step, 15);
table[i] = (table[i] & ~mask) | (update << offset);
return true;
}
return false;
}
/**
* Returns the table index for the counter at the specified depth.
*
* @param item the element's hash
* @param i the counter depth
* @return the table index
*/
int indexOf(int item, int i) {
long hash = SEED[i] * item;
hash += hash >> 32;
return ((int) hash) & tableMask;
}
/**
* Applies a supplemental hash function to a given hashCode, which defends against poor quality
* hash functions.
*/
int spread(int x) {
x = ((x >>> 16) ^ x) * 0x45d9f3b;
x = ((x >>> 16) ^ x) * randomSeed;
return (x >>> 16) ^ x;
}
static int ceilingNextPowerOfTwo(int x) {
// From Hacker's Delight, Chapter 3, Harry S. Warren Jr.
return 1 << -Integer.numberOfLeadingZeros(x - 1);
}
}