package org.cache2k.benchmark.impl2015;
/*
* #%L
* Benchmarks: implementation variants
* %%
* Copyright (C) 2013 - 2017 headissue GmbH, Munich
* %%
* 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.
* #L%
*/
import org.cache2k.benchmark.impl2015.util.TunableConstants;
import org.cache2k.benchmark.impl2015.util.TunableFactory;
/**
* CLOCK Pro implementation with 3 clocks. Using separate clocks for hot and cold
* has saves us the extra marker (4 bytes in Java) for an entry to decide whether it is in hot
* or cold. OTOH we shuffle around the entries in different lists and loose the order they
* were inserted, which leads to less cache efficiency.
*
* <p/>This version uses a static allocation for hot and cold spaces. No online or dynamic
* optimization is done yet. However, the hitrate for all measured access traces is better
* then LRU and it is resistant to scans.
*
* @author Jens Wilke; created: 2013-07-12
*/
@SuppressWarnings("unchecked")
public class ClockProPlusCache<K, T> extends LockFreeCache<Entry, K, T> {
private static final Tunable TUNABLE_CLOCK_PRO = TunableFactory.get(Tunable.class);
long hotHits;
long coldHits;
long ghostHits;
long directRemoveCnt;
long hotRunCnt;
long hot24hCnt;
long hotScanCnt;
long hotSizeSum;
long coldRunCnt;
long cold24hCnt;
long coldScanCnt;
int coldSize;
int hotSize;
int staleSize;
/** Maximum size of hot clock. 0 means normal clock behaviour */
int hotMax;
int ghostMax;
Entry handCold;
Entry handHot;
Entry handGhost;
Hash<Entry> ghostHashCtrl;
Entry[] ghostHash;
long ghostInsertCnt = 0;
private long sumUpListHits(Entry e) {
if (e == null) { return 0; }
long cnt = 0;
Entry _head = e;
do {
cnt += e.hitCnt;
e = (Entry) e.next;
} while (e != _head);
return cnt;
}
@Override
public long getHitCnt() {
return hotHits + coldHits + sumUpListHits(handCold) + sumUpListHits(handHot);
}
protected void initializeHeapCache() {
super.initializeHeapCache();
ghostMax = maxSize;
hotMax = maxSize * TUNABLE_CLOCK_PRO.hotMaxPercentage / 100;
coldSize = 0;
hotSize = 0;
staleSize = 0;
handCold = null;
handHot = null;
handGhost = null;
ghostHashCtrl = new Hash<Entry>();
ghostHash = ghostHashCtrl.init(Entry.class);
}
@Override
protected void iterateAllEntriesRemoveAndCancelTimer() {
Entry e, _head;
int _count = 0;
e = _head = handCold;
long _hits = 0;
if (e != null) {
do {
_hits += e.hitCnt;
if (!e.isStale()) {
e.removedFromList();
cancelExpiryTimer(e);
_count++;
}
e = (Entry) e.prev;
} while (e != _head);
coldHits += _hits;
}
e = _head = handHot;
if (e != null) {
_hits = 0;
do {
_hits += e.hitCnt;
if (!e.isStale()) {
e.removedFromList();
cancelExpiryTimer(e);
_count++;
}
e = (Entry) e.prev;
} while (e != _head);
hotHits += _hits;
}
}
/**
* We are called to remove the entry. The cause may be a timer event
* or for eviction.
* We can just remove the entry from the list, but we don't
* know which list it is in to correct the counters accordingly.
* So, instead of removing it directly, we just mark it and remove it
* by the normal eviction process.
*/
@Override
protected void removeEntryFromReplacementList(Entry e) {
insertCopyIntoGhosts(e);
if (handCold == e) {
coldHits += e.hitCnt;
handCold = removeFromCyclicList(handCold, e);
coldSize--;
directRemoveCnt++;
} else {
staleSize++;
e.setStale();
}
}
private void insertCopyIntoGhosts(Entry e) {
Entry<Entry, K,T> e2 = new Entry<Entry, K, T>();
e2.key = (K) e.key;
e2.hashCode = e.hashCode;
e2.fetchedTime = ghostInsertCnt++;
ghostHash = ghostHashCtrl.insert(ghostHash, e2);
handGhost = insertIntoTailCyclicList(handGhost, e2);
if (ghostHashCtrl.size > ghostMax) {
runHandGhost();
}
}
private int getListSize() {
return hotSize + coldSize - staleSize;
}
@Override
protected void recordHit(Entry e) {
long _hitCnt = e.hitCnt + 1;
if ((_hitCnt & 0x100000000L) != 0 ) {
scrubCache(e);
recordHit(e);
return;
}
e.hitCnt = _hitCnt;
}
private long scrubCounters(Entry e) {
if (e == null) { return 0; }
int cnt = 0;
Entry _head = e;
do {
long _hitCnt = e.hitCnt;
long _hitCnt2 = e.hitCnt = e.hitCnt >> 1;
cnt += _hitCnt - _hitCnt2;
e = (Entry) e.next;
} while (e != _head);
return cnt;
}
protected void scrubCache(Entry e) {
synchronized (lock) {
if (e.hitCnt != Integer.MAX_VALUE) {
return;
}
coldHits += scrubCounters(handCold);
hotHits += scrubCounters(handHot);
}
}
@Override
protected void insertIntoReplacementList(Entry e) {
coldSize++;
handCold = insertIntoTailCyclicList(handCold, e);
}
@Override
protected Entry newEntry() {
return new Entry();
}
protected Entry<Entry, K,T> runHandHot() {
hotRunCnt++;
Entry<Entry, K,T> _handStart = handHot;
Entry<Entry, K,T> _hand = _handStart;
Entry<Entry, K,T> _coldCandidate = _hand;
long _lowestHits = Long.MAX_VALUE;
long _hotHits = hotHits;
int _scanCnt = -1;
long _decrease = ((_hand.hitCnt + _hand.next.hitCnt) >> TUNABLE_CLOCK_PRO.hitCounterDecreaseShift) + 1;
do {
_scanCnt++;
long _hitCnt = _hand.hitCnt;
if (_hitCnt < _lowestHits) {
_lowestHits = _hitCnt;
_coldCandidate = _hand;
if (_hitCnt == 0) {
break;
}
}
if (_hitCnt < _decrease) {
_hand.hitCnt = 0;
_hotHits += _hitCnt;
} else {
_hand.hitCnt = _hitCnt - _decrease;
_hotHits += _decrease;
}
_hand = _hand.next;
} while (_hand != _handStart);
hotHits = _hotHits;
hotScanCnt += _scanCnt;
if (_scanCnt == hotMax ) {
hot24hCnt++; // count a full clock cycle
}
handHot = removeFromCyclicList(_hand, _coldCandidate);
hotSize--;
return _coldCandidate;
}
/**
* Runs cold hand an in turn hot hand to find eviction candidate.
*/
@Override
protected Entry findEvictionCandidate() {
hotSizeSum += hotMax;
coldRunCnt++;
Entry<Entry, K,T> _hand = handCold;
int _scanCnt = 0;
do {
if (_hand == null) {
_hand = refillFromHot(_hand);
}
if (_hand.hitCnt > 0) {
_hand = refillFromHot(_hand);
do {
_scanCnt++;
coldHits += _hand.hitCnt;
_hand.hitCnt = 0;
Entry<Entry, K, T> e = _hand;
_hand = (Entry<Entry, K, T>) removeFromCyclicList(e);
coldSize--;
hotSize++;
handHot = insertIntoTailCyclicList(handHot, e);
} while (_hand != null && _hand.hitCnt > 0);
}
if (_hand == null) {
_hand = refillFromHot(_hand);
}
if (!_hand.isStale()) {
break;
}
_hand = (Entry<Entry, K,T>) removeFromCyclicList(_hand);
staleSize--;
coldSize--;
_scanCnt--;
} while (true);
if (_scanCnt > this.coldSize) {
cold24hCnt++;
}
coldScanCnt += _scanCnt;
handCold = _hand;
return _hand;
}
private Entry<Entry, K, T> refillFromHot(Entry<Entry, K, T> _hand) {
while (hotSize > hotMax || _hand == null) {
Entry<Entry, K,T> e = runHandHot();
if (e != null) {
if (e.isStale()) {
staleSize--;
} else {
_hand = insertIntoTailCyclicList(_hand, e);
coldSize++;
}
}
}
return _hand;
}
protected void runHandGhost() {
boolean f = ghostHashCtrl.remove(ghostHash, handGhost);
Entry e = handGhost;
handGhost = (Entry) removeFromCyclicList(handGhost);
}
@Override
protected Entry checkForGhost(K key, int hc) {
Entry e = ghostHashCtrl.remove(ghostHash, key, hc);
if (e != null) {
handGhost = removeFromCyclicList(handGhost, e);
ghostHits++;
hotSize++;
handHot = insertIntoTailCyclicList(handHot, e);
}
return e;
}
@Override
protected IntegrityState getIntegrityState() {
synchronized (lock) {
return super.getIntegrityState()
.checkEquals("ghostHashCtrl.size == Hash.calcEntryCount(refreshHash)",
ghostHashCtrl.size, Hash.calcEntryCount(ghostHash))
.check("hotMax <= maxElements", hotMax <= maxSize)
.checkEquals("getListSize() == getSize()", (getListSize()) , getLocalSize())
.check("checkCyclicListIntegrity(handHot)", checkCyclicListIntegrity(handHot))
.check("checkCyclicListIntegrity(handCold)", checkCyclicListIntegrity(handCold))
.check("checkCyclicListIntegrity(handGhost)", checkCyclicListIntegrity(handGhost))
.checkEquals("getCyclicListEntryCount(handHot) == hotSize", getCyclicListEntryCount(handHot), hotSize)
.checkEquals("getCyclicListEntryCount(handCold) == coldSize", getCyclicListEntryCount(handCold), coldSize)
.checkEquals("getCyclicListEntryCount(handGhost) == ghostSize", getCyclicListEntryCount(handGhost), ghostHashCtrl.size);
}
}
@Override
protected String getExtraStatistics() {
return ", coldSize=" + coldSize +
", hotSize=" + hotSize +
", hotMaxSize=" + hotMax +
", ghostSize=" + ghostHashCtrl.size +
", staleSize=" + staleSize +
", coldHits=" + (coldHits + sumUpListHits(handCold)) +
", hotHits=" + (hotHits + sumUpListHits(handHot)) +
", ghostHits=" + ghostHits +
", coldRunCnt=" + coldRunCnt +// identical to the evictions anyways
", coldScanCnt=" + coldScanCnt +
", cold24hCnt=" + cold24hCnt +
", hotRunCnt=" + hotRunCnt +
", hotScanCnt=" + hotScanCnt +
", hot24hCnt=" + hot24hCnt +
", directRemoveCnt=" + directRemoveCnt;
}
public static class Tunable extends TunableConstants {
int hotMaxPercentage = 97;
int hitCounterDecreaseShift = 6;
}
}