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}