/*
* Copyright 2015 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.tinycache;
import java.util.Random;
/**
* This is the TinyCache model that takes advantage of random eviction policy with
* a ghost cache as admission policy. It offers a very dense memory layout combined with
* (relative) speed at the expense of limited associativity. s
*
* @author gilga1983@gmail.com (Gil Einziger)
*/
@SuppressWarnings("PMD.AvoidDollarSigns")
public final class TinyCache {
public final long[] chainIndex;
public final long[] isLastIndex;
private final HashFunctionParser hashFunc;
private final int itemsPerSet;
private final long[] cache;
private final Random rnd;
public TinyCache(int nrSets, int itemsPerSet, int randomSeed) {
chainIndex = new long[nrSets];
isLastIndex = new long[nrSets];
hashFunc = new HashFunctionParser(nrSets);
this.itemsPerSet = itemsPerSet;
cache = new long[nrSets * itemsPerSet];
rnd = new Random(randomSeed);
}
public boolean contains(long item) {
hashFunc.createHash(item);
if (!TinySetIndexing.chainExist(chainIndex[hashFunc.fpaux.set], hashFunc.fpaux.chainId)) {
return false;
}
TinySetIndexing.getChain(hashFunc.fpaux, chainIndex, isLastIndex);
int offset = this.itemsPerSet * hashFunc.fpaux.set;
TinySetIndexing.chainStart += offset;
TinySetIndexing.chainEnd += offset;
// Gil : I think some of these tests are, I till carefully examine this function when I have
// time. As far as I understand it is working right now.
while (TinySetIndexing.chainStart <= TinySetIndexing.chainEnd) {
try {
if (cache[TinySetIndexing.chainStart % cache.length] == hashFunc.fpaux.value) {
return true;
}
TinySetIndexing.chainStart++;
} catch (Exception e) {
System.out.println(" length: " + cache.length + " Access: " + TinySetIndexing.chainStart);
}
}
return false;
}
/**
* Implementing add and remove together in one function, means that less items are shifted.
* (reduction of 3 times from trivial implementation).
*/
private int replace(HashedItem fpaux, byte victim, int bucketStart, int removedOffset) {
byte chainId = fpaux.chainId;
fpaux.chainId = victim;
this.cache[bucketStart + removedOffset] = 0;
TinySetIndexing.removeItem(fpaux, chainIndex, isLastIndex);
fpaux.chainId = chainId;
int idxToAdd = TinySetIndexing.addItem(fpaux, chainIndex, isLastIndex);
int delta = (removedOffset < idxToAdd) ? -1 : 1;
replaceItems(idxToAdd, fpaux.value, bucketStart, delta);
return removedOffset;
}
public boolean addItem(long item) {
hashFunc.createHash(item);
int bucketStart = this.itemsPerSet * hashFunc.fpaux.set;
if (cache[bucketStart + this.itemsPerSet - 1] != 0) {
return selectVictim(bucketStart);
}
int idxToAdd = TinySetIndexing.addItem(hashFunc.fpaux, chainIndex, isLastIndex);
this.replaceItems(idxToAdd, hashFunc.fpaux.value, bucketStart, 1);
return false;
}
private boolean selectVictim(int bucketStart) {
byte victimOffset = (byte) rnd.nextInt(this.itemsPerSet);
int victimChain =
TinySetIndexing.getChainAtOffset(hashFunc.fpaux, chainIndex, isLastIndex, victimOffset);
// this if is still for debugging and common sense. Should be eliminated for performance once
// I am sure of the correctness.
if (TinySetIndexing.chainExist(chainIndex[hashFunc.fpaux.set], victimChain)) {
replace(hashFunc.fpaux, (byte) victimChain, bucketStart, victimOffset);
return true;
} else {
throw new RuntimeException("Failed to replace");
}
}
private void replaceItems(final int idx, long value, int start, final int delta) {
start += idx;
long $;
do {
$ = this.cache[start];
this.cache[start] = value;
value = $;
start += delta;
} while (value != 0);
}
}