001
002package org.cache2k.impl;
003
004/*
005 * #%L
006 * cache2k core package
007 * %%
008 * Copyright (C) 2000 - 2015 headissue GmbH, Munich
009 * %%
010 * This program is free software: you can redistribute it and/or modify
011 * it under the terms of the GNU General Public License as
012 * published by the Free Software Foundation, either version 3 of the 
013 * License, or (at your option) any later version.
014 * 
015 * This program is distributed in the hope that it will be useful,
016 * but WITHOUT ANY WARRANTY; without even the implied warranty of
017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
018 * GNU General Public License for more details.
019 * 
020 * You should have received a copy of the GNU General Public 
021 * License along with this program.  If not, see
022 * <http://www.gnu.org/licenses/gpl-3.0.html>.
023 * #L%
024 */
025
026/**
027 * Adaptive Replacement Cache implementation for cache2k. The algorithm itself is patented by IBM, so the
028 * implementation will probably never be available or used as default eviction algorithm. This implementation
029 * may be removed from the cache2k core package before 1.0.
030 *
031 * <p/>The implemented algorithm changed from cache2k version 0.19 to 0.20. Within version 0.20
032 * the eviction is separated from the general cache access, because of the consequent entry locking
033 * scheme an entry that is accessed, is always present in the cache. This means, the eviction is
034 * postponed to the end of the cache operation. The ARC algorithm was augmented to reflect this.
035 * This means for a cache operation that a newly inserted cache entry is also a candidate
036 * for eviction at the end of the operation.
037 *
038 * @see <a href="http://www.usenix.org/event/fast03/tech/full_papers/megiddo/megiddo.pdf‎">A Self-Tuning, Low Overhead Replacement Cache</a>
039 * @see <a href="http://en.wikipedia.org/wiki/Adaptive_replacement_cache">Wikipedia: Adaptive Replacement Cache</a>
040 *
041 * @author Jens Wilke
042 */
043@SuppressWarnings("unchecked")
044public class ArcCache<K, T> extends BaseCache<ArcCache.Entry, K, T> {
045
046  int arcP = 0;
047
048  Hash<Entry> b1HashCtrl;
049  Entry[] b1Hash;
050
051  Hash<Entry> b2HashCtrl;
052  Entry[] b2Hash;
053
054  /** Statistics */
055  long t2Hit;
056  long t1Hit;
057
058  int t1Size = 0;
059  Entry<K,T> t2Head;
060  Entry<K,T> t1Head;
061  Entry<K,T> b1Head;
062  Entry<K,T> b2Head;
063
064  boolean b2HitPreferenceForEviction;
065
066  @Override
067  public long getHitCnt() {
068    return t2Hit + t1Hit;
069  }
070
071  @Override
072  protected void recordHit(Entry e) {
073    moveToFront(t2Head, e);
074    if (e.withinT2) {
075      t2Hit++;
076    } else {
077      e.withinT2 = true;
078      t1Hit++;
079      t1Size--;
080    }
081  }
082
083  @Override
084  protected void insertIntoReplacementList(Entry e) {
085    insertInList(t1Head, e);
086    t1Size++;
087  }
088
089  /** Emtpy, done by replace / checkForGhost. */
090
091  @Override
092  protected Entry<K, T> newEntry() {
093    return new Entry<K, T>();
094  }
095
096  @Override
097  protected Entry checkForGhost(K key, int hc) {
098    Entry e = b1HashCtrl.remove(b1Hash, key, hc);
099    if (e != null) {
100      removeFromList(e);
101      b1HitAdaption();
102      insertT2(e);
103      return e;
104    }
105    e = b2HashCtrl.remove(b2Hash, key, hc);
106    if (e != null) {
107      removeFromList(e);
108      b2HitAdaption();
109      insertT2(e);
110      return e;
111    }
112    allMissEvictGhots();
113    return null;
114  }
115
116  @Override
117  protected void removeEntryFromReplacementList(Entry e) {
118    if (!e.withinT2) {
119      t1Size--;
120    }
121    super.removeEntryFromReplacementList(e);
122    e.withinT2 = false;
123  }
124
125  private void b1HitAdaption() {
126    int _b1Size = b1HashCtrl.size + 1;
127    int _b2Size = b2HashCtrl.size;
128    int _delta = _b1Size >= _b2Size ? 1 : _b2Size / _b1Size;
129    arcP = Math.min(arcP + _delta, maxSize);
130    b2HitPreferenceForEviction = false;
131  }
132
133  private void b2HitAdaption() {
134    int _b1Size = b1HashCtrl.size;
135    int _b2Size = b2HashCtrl.size + 1;
136    int _delta = _b2Size >= _b1Size ? 1 : _b1Size / _b2Size;
137    arcP = Math.max(arcP - _delta, 0);
138    b2HitPreferenceForEviction = true;
139  }
140
141  private void insertT2(Entry<K, T> e) {
142    e.withinT2 = true;
143    insertInList(t2Head, e);
144  }
145
146  int getT2Size() {
147    return getLocalSize() - t1Size;
148  }
149
150  Entry cloneGhost(Entry e) {
151    Entry e2 = new Entry();
152    e2.hashCode = e.hashCode;
153    e2.key = e.key;
154    return e2;
155  }
156
157  /**
158   * Called when no entry was hit within b1 or b2. This checks whether we need to
159   * remove some entries from the b1 and b2 lists.
160   */
161  private void allMissEvictGhots() {
162    if ((t1Size + b1HashCtrl.size) >= maxSize) {
163      if (b1HashCtrl.size > 0) {
164        Entry e = b1Head.prev;
165        removeFromList(e);
166        boolean f = b1HashCtrl.remove(b1Hash, e);
167      } else {
168        if (b2HashCtrl.size >= maxSize) {
169          Entry e = b2Head.prev;
170          removeFromList(e);
171          boolean f = b2HashCtrl.remove(b2Hash, e);
172        }
173      }
174    } else {
175      int _totalCnt = b1HashCtrl.size + b2HashCtrl.size;
176      if (_totalCnt >= maxSize) {
177        if (b2HashCtrl.size == 0) {
178          Entry e = b1Head.prev;
179          removeFromList(e);
180          boolean f = b1HashCtrl.remove(b1Hash, e);
181          return;
182        }
183        Entry e = b2Head.prev;
184        removeFromList(e);
185        boolean f = b2HashCtrl.remove(b2Hash, e);
186      }
187    }
188  }
189
190  @Override
191  protected Entry findEvictionCandidate() {
192    Entry e;
193    if (b2HitPreferenceForEviction) {
194      e = replaceB2Hit();
195    } else {
196      e = replace();
197    }
198    if (b1HashCtrl.size + b2HashCtrl.size > maxSize) {
199      allMissEvictGhots();
200    }
201    return e;
202  }
203
204  private Entry replace() {
205    return replace((t1Size > arcP) || getT2Size() == 0);
206  }
207
208  private Entry replaceB2Hit() {
209    return replace((t1Size >= arcP && t1Size > 0) || getT2Size() == 0);
210  }
211
212  private Entry<K,T> replace(boolean _fromT1) {
213    Entry<K,T> e;
214    if (_fromT1) {
215      e = t1Head.prev;
216      Entry<K,T> e2 = cloneGhost(e);
217      insertInList(b1Head, e2);
218      b1Hash = b1HashCtrl.insert(b1Hash, e2);
219    } else {
220      e = t2Head.prev;
221      Entry<K,T> e2 = cloneGhost(e);
222      insertInList(b2Head, e2);
223      b2Hash = b2HashCtrl.insert(b2Hash, e2);
224    }
225    return e;
226  }
227
228  @Override
229  protected void initializeHeapCache() {
230    super.initializeHeapCache();
231    t1Size = 0;
232    b1HashCtrl = new Hash<Entry>();
233    b2HashCtrl = new Hash<Entry>();
234    b1Hash = b1HashCtrl.init(Entry.class);
235    b2Hash = b2HashCtrl.init(Entry.class);
236    t1Head = new Entry<K,T>().shortCircuit();
237    t2Head = new Entry<K,T>().shortCircuit();
238    b1Head = new Entry<K,T>().shortCircuit();
239    b2Head = new Entry<K,T>().shortCircuit();
240  }
241
242  final int getListEntryCount() {
243    return getListEntryCount(t1Head) + getListEntryCount(t2Head);
244  }
245
246  @Override
247  protected String getExtraStatistics() {
248    return  ", arcP=" + arcP + ", "
249          + "t1Size=" + t1Size + ", "
250          + "t2Size=" + (mainHashCtrl.size - t1Size) + ", "
251          + "b1Size=" + b1HashCtrl.size + ", "
252          + "b2Size=" + b2HashCtrl.size ;
253  }
254
255  @Override
256  protected IntegrityState getIntegrityState() {
257    return super.getIntegrityState()
258      .check("getSize() == getHashEntryCount()", getLocalSize() == calculateHashEntryCount())
259      .check("getSize() == getListEntryCount()", getLocalSize() == getListEntryCount())
260      .checkEquals("t1Size == getListEntryCount(t1Head)", t1Size, getListEntryCount(t1Head))
261      .checkEquals("getSize() - t1Size == getListEntryCount(t2Head)", getLocalSize() - t1Size, getListEntryCount(t2Head))
262      .checkLessOrEquals(
263        "b1HashCtrl.size + b2HashCtrl.size <= maxSize",
264        b1HashCtrl.size + b2HashCtrl.size, maxSize)
265      .checkEquals("b1HashCtrl.size == getListEntryCount(b1Head)", b1HashCtrl.size, getListEntryCount(b1Head))
266      .checkEquals("b2HashCtrl.size == getListEntryCount(b2Head)", b2HashCtrl.size, getListEntryCount(b2Head));
267  }
268
269  /** An entry in the hash table */
270  protected static class Entry<K,T> extends org.cache2k.impl.Entry<Entry<K,T>, K, T> {
271
272    boolean withinT2;
273
274  }
275
276}