001package org.cache2k.impl; 002 003/* 004 * #%L 005 * cache2k core package 006 * %% 007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich 008 * %% 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as 011 * published by the Free Software Foundation, either version 3 of the 012 * License, or (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public 020 * License along with this program. If not, see 021 * <http://www.gnu.org/licenses/gpl-3.0.html>. 022 * #L% 023 */ 024 025import org.cache2k.impl.util.TunableConstants; 026 027/** 028 * CLOCK Pro implementation with 3 clocks. Using separate clocks for hot and cold 029 * has saves us the extra marker (4 bytes in Java) for an entry to decide whether it is in hot 030 * or cold. OTOH we shuffle around the entries in different lists and loose the order they 031 * were inserted, which leads to less cache efficiency. 032 * 033 * <p/>This version uses a static allocation for hot and cold spaces. No online or dynamic 034 * optimization is done yet. However, the hitrate for all measured access traces is better 035 * then LRU and it is resistant to scans. 036 * 037 * @author Jens Wilke; created: 2013-07-12 038 */ 039@SuppressWarnings("unchecked") 040public class ClockProPlusCache<K, T> extends LockFreeCache<ClockProPlusCache.Entry, K, T> { 041 042 long hotHits; 043 long coldHits; 044 long ghostHits; 045 long directRemoveCnt; 046 047 long hotRunCnt; 048 long hot24hCnt; 049 long hotScanCnt; 050 051 long evictedColdHitCnt = 0; 052 053 long hotSizeSum; 054 long coldRunCnt; 055 long cold24hCnt; 056 long coldScanCnt; 057 058 int coldSize; 059 int hotSize; 060 int staleSize; 061 062 /** Maximum size of hot clock. 0 means normal clock behaviour */ 063 int hotMax; 064 int ghostMax; 065 066 Entry handCold; 067 Entry handHot; 068 Entry handGhost; 069 Hash<Entry> ghostHashCtrl; 070 Entry[] ghostHash; 071 long ghostInsertCnt = 0; 072 073 Tunable tunable = new Tunable(); 074 075 private int sumUpListHits(Entry e) { 076 if (e == null) { return 0; } 077 int cnt = 0; 078 Entry _head = e; 079 do { 080 cnt += e.hitCnt; 081 e = (Entry) e.next; 082 } while (e != _head); 083 return cnt; 084 } 085 086 @Override 087 public long getHitCnt() { 088 return hotHits + coldHits + sumUpListHits(handCold) + sumUpListHits(handHot); 089 } 090 091 protected void initializeHeapCache() { 092 super.initializeHeapCache(); 093 ghostMax = maxSize; 094 ghostMax = maxSize; 095 hotMax = maxSize * 97 / 100; 096 coldSize = 0; 097 hotSize = 0; 098 staleSize = 0; 099 handCold = null; 100 handHot = null; 101 handGhost = null; 102 ghostHashCtrl = new Hash<Entry>(); 103 ghostHash = ghostHashCtrl.init(Entry.class); 104 } 105 106 107 /** 108 * We are called to remove the entry. The cause may be a timer event 109 * or for eviction. 110 * We can just remove the entry from the list, but we don't 111 * know which list it is in to correct the counters accordingly. 112 * So, instead of removing it directly, we just mark it and remove it 113 * by the normal eviction process. 114 */ 115 @Override 116 protected void removeEntryFromReplacementList(Entry e) { 117 evictedColdHitCnt += e.coldHitCnt; 118 insertCopyIntoGhosts(e); 119 if (handCold == e) { 120 handCold = removeFromCyclicList(handCold, e); 121 coldSize--; 122 directRemoveCnt++; 123 } else { 124 staleSize++; 125 e.setStale(); 126 } 127 } 128 129 private void insertCopyIntoGhosts(Entry e) { 130 Entry<K,T> e2 = new Entry<K, T>(); 131 e2.key = (K) e.key; 132 e2.hashCode = e.hashCode; 133 e2.fetchedTime = ghostInsertCnt++; 134 ghostHash = ghostHashCtrl.insert(ghostHash, e2); 135 handGhost = insertIntoTailCyclicList(handGhost, e2); 136 if (ghostHashCtrl.size > ghostMax) { 137 runHandGhost(); 138 } 139 } 140 141 private int getListSize() { 142 return hotSize + coldSize - staleSize; 143 } 144 145 @Override 146 protected void recordHit(Entry e) { 147 e.hitCnt++; 148 } 149 150 @Override 151 protected void insertIntoReplacementList(Entry e) { 152 coldSize++; 153 handCold = insertIntoTailCyclicList(handCold, e); 154 } 155 156 @Override 157 protected Entry newEntry() { 158 return new Entry(); 159 } 160 161 protected Entry<K,T> runHandHot() { 162 hotRunCnt++; 163 Entry<K,T> _handStart = handHot; 164 Entry<K,T> _hand = _handStart; 165 Entry<K,T> _coldCandidate = _hand; 166 int _lowestHits = Integer.MAX_VALUE; 167 long _hotHits = hotHits; 168 int _scanCnt = -1; 169 int _decrease = 1; 170 _decrease = ((_hand.hitCnt + _hand.next.hitCnt) >> 6) + 1; 171 do { 172 _scanCnt++; 173 int _hitCnt = _hand.hitCnt; 174 if (_hitCnt < _lowestHits) { 175 _lowestHits = _hitCnt; 176 _coldCandidate = _hand; 177 if (_hitCnt == 0) { 178 break; 179 } 180 } 181 if (_hitCnt < _decrease) { 182 _hand.hitCnt = 0; 183 _hotHits += _hitCnt; 184 } else { 185 _hand.hitCnt = _hitCnt - _decrease; 186 _hotHits += _decrease; 187 } 188 _hand = _hand.next; 189 } while (_hand != _handStart); 190 hotHits = _hotHits; 191 hotScanCnt += _scanCnt; 192 if (_scanCnt == hotMax ) { 193 hot24hCnt++; // count a full clock cycle 194 } 195 handHot = removeFromCyclicList(_hand, _coldCandidate); 196 hotSize--; 197 return _coldCandidate; 198 } 199 200 /** 201 * Runs cold hand an in turn hot hand to find eviction candidate. 202 */ 203 @Override 204 protected Entry findEvictionCandidate() { 205 hotSizeSum += hotMax; 206 coldRunCnt++; 207 Entry<K,T> _hand = handCold; 208 int _scanCnt = 0; 209 do { 210 if (_hand == null) { 211 _hand = refillFromHot(_hand); 212 } 213 if (_hand.hitCnt > 0) { 214 _hand = refillFromHot(_hand); 215 do { 216 _scanCnt++; 217 _hand.coldHitCnt++; 218 coldHits += _hand.hitCnt; 219 _hand.hitCnt = 0; 220 Entry<K, T> e = _hand; 221 _hand = (Entry<K, T>) removeFromCyclicList(e); 222 coldSize--; 223 hotSize++; 224 handHot = insertIntoTailCyclicList(handHot, e); 225 } while (_hand != null && _hand.hitCnt > 0); 226 } 227 228 if (_hand == null) { 229 _hand = refillFromHot(_hand); 230 } 231 232 if (!_hand.isStale()) { 233 break; 234 } 235 _hand = (Entry<K,T>) removeFromCyclicList(_hand); 236 staleSize--; 237 coldSize--; 238 _scanCnt--; 239 240 } while (true); 241 if (_scanCnt > this.coldSize) { 242 cold24hCnt++; 243 } 244 coldScanCnt += _scanCnt; 245 handCold = _hand; 246 return _hand; 247 } 248 249 private Entry<K, T> refillFromHot(Entry<K, T> _hand) { 250 while (hotSize > hotMax || _hand == null) { 251 Entry<K,T> e = runHandHot(); 252 if (e != null) { 253 if (e.isStale()) { 254 staleSize--; 255 } else { 256 _hand = insertIntoTailCyclicList(_hand, e); 257 coldSize++; 258 } 259 } 260 } 261 return _hand; 262 } 263 264 protected void runHandGhost() { 265 boolean f = ghostHashCtrl.remove(ghostHash, handGhost); 266 Entry e = handGhost; 267 handGhost = (Entry) removeFromCyclicList(handGhost); 268 } 269 270 @Override 271 protected Entry checkForGhost(K key, int hc) { 272 Entry e = ghostHashCtrl.remove(ghostHash, key, hc); 273 if (e != null) { 274 handGhost = removeFromCyclicList(handGhost, e); 275 ghostHits++; 276 hotSize++; 277 handHot = insertIntoTailCyclicList(handHot, e); 278 } 279 return e; 280 } 281 282 @Override 283 protected IntegrityState getIntegrityState() { 284 synchronized (lock) { 285 return super.getIntegrityState() 286 .checkEquals("ghostHashCtrl.size == Hash.calcEntryCount(refreshHash)", 287 ghostHashCtrl.size, Hash.calcEntryCount(ghostHash)) 288 .check("hotMax <= maxElements", hotMax <= maxSize) 289 .checkEquals("getListSize() == getSize()", (getListSize()) , getLocalSize()) 290 .check("checkCyclicListIntegrity(handHot)", checkCyclicListIntegrity(handHot)) 291 .check("checkCyclicListIntegrity(handCold)", checkCyclicListIntegrity(handCold)) 292 .check("checkCyclicListIntegrity(handGhost)", checkCyclicListIntegrity(handGhost)) 293 .checkEquals("getCyclicListEntryCount(handHot) == hotSize", getCyclicListEntryCount(handHot), hotSize) 294 .checkEquals("getCyclicListEntryCount(handCold) == coldSize", getCyclicListEntryCount(handCold), coldSize) 295 .checkEquals("getCyclicListEntryCount(handGhost) == ghostSize", getCyclicListEntryCount(handGhost), ghostHashCtrl.size); 296 } 297 } 298 299 @Override 300 protected String getExtraStatistics() { 301 return ", coldSize=" + coldSize + 302 ", hotSize=" + hotSize + 303 ", hotMaxSize=" + hotMax + 304 ", hotSizeAvg=" + (coldRunCnt > 0 ? (hotSizeSum / coldRunCnt) : -1) + 305 ", ghostSize=" + ghostHashCtrl.size + 306 ", staleSize=" + staleSize + 307 ", coldHits=" + (coldHits + sumUpListHits(handCold)) + 308 ", hotHits=" + (hotHits + sumUpListHits(handHot)) + 309 ", ghostHits=" + ghostHits + 310 ", coldRunCnt=" + coldRunCnt +// identical to the evictions anyways 311 ", coldScanCnt=" + coldScanCnt + 312 ", cold24hCnt=" + cold24hCnt + 313 ", hotRunCnt=" + hotRunCnt + 314 ", hotScanCnt=" + hotScanCnt + 315 ", hot24hCnt=" + hot24hCnt + 316 ", directRemoveCnt=" + directRemoveCnt; 317 } 318 319 static class Entry<K, T> extends org.cache2k.impl.Entry<Entry, K, T> { 320 321 int hitCnt; 322 int coldHitCnt; 323 324 } 325 326 public int getHotMax() { 327 return hotMax; 328 } 329 330 public void setHotMax(int hotMax) { 331 this.hotMax = hotMax; 332 } 333 334 public static class Tunable extends TunableConstants { 335 336 public boolean insert0HitsFromHotToColdHead = false; 337 338 } 339 340}