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}