KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > tc > object > cache > LFUEvictionPolicy


1 /*
2  * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
3  * notice. All rights reserved.
4  */

5 package com.tc.object.cache;
6
7 import com.tc.logging.TCLogger;
8 import com.tc.logging.TCLogging;
9 import com.tc.text.PrettyPrinter;
10
11 import gnu.trove.TLinkedList;
12 import gnu.trove.TObjectLongHashMap;
13
14 import java.util.ArrayList JavaDoc;
15 import java.util.Arrays JavaDoc;
16 import java.util.Collection JavaDoc;
17 import java.util.Collections JavaDoc;
18 import java.util.HashSet JavaDoc;
19 import java.util.Iterator JavaDoc;
20 import java.util.Map JavaDoc;
21 import java.util.TreeMap JavaDoc;
22 import java.util.Map.Entry;
23
24 /**
25  * This Cache policy tries to evict objects from cache using the access count. The Least Frequenctly used objects are
26  * chucked out.
27  */

28 public class LFUEvictionPolicy implements EvictionPolicy {
29
30   private static final TCLogger logger = TCLogging.getLogger(LFUEvictionPolicy.class);
31
32   private static final LFUConfig DEFAULT_CONFIG = new LFUConfig() {
33
34                                                   public float getAgingFactor() {
35                                                     // DISABLED
36
return 1;
37                                                   }
38
39                                                   public int getRecentlyAccessedIgnorePercentage() {
40                                                     return 20;
41                                                   }
42
43                                                   public boolean isDebugEnabled() {
44                                                     return false;
45                                                   }
46
47                                                 };
48
49   private final int capacity;
50   private final int evictionSize;
51   private final TLinkedList cache = new TLinkedList();
52   private final LFUConfig config;
53
54   private TObjectLongHashMap smap = new TObjectLongHashMap();
55
56   public LFUEvictionPolicy(int capacity) {
57     this(capacity, capacity / 10, DEFAULT_CONFIG);
58   }
59
60   public LFUEvictionPolicy(int capacity, LFUConfig config) {
61     this(capacity, capacity / 10, config);
62   }
63
64   public LFUEvictionPolicy(int capacity, int evictionSize) {
65     this(capacity, evictionSize, DEFAULT_CONFIG);
66   }
67
68   public LFUEvictionPolicy(int capacity, int evictionSize, LFUConfig config) {
69     if (logger.isDebugEnabled()) {
70       logger.debug("new " + getClass().getName() + "(" + capacity + ")");
71     }
72     this.capacity = capacity;
73     this.evictionSize = (evictionSize <= 0 ? 1 : evictionSize);
74     this.config = config;
75   }
76
77   public synchronized boolean add(Cacheable obj) {
78     cache.addLast(obj);
79     if (config.isDebugEnabled()) smap.put(obj.getObjectID(), System.currentTimeMillis());
80     return isCacheFull();
81   }
82
83   private boolean isCacheFull() {
84     return (capacity > 0 && cache.size() > capacity);
85   }
86
87   public int getCacheCapacity() {
88     return capacity;
89   }
90
91   public synchronized void markReferenced(Cacheable obj) {
92     obj.markAccessed();
93     moveToTail(obj);
94   }
95
96   private void moveToTail(Cacheable obj) {
97     if (contains(obj)) {
98       cache.remove(obj);
99       cache.addLast(obj);
100     }
101   }
102
103   public Collection JavaDoc getRemovalCandidates(int maxCount) {
104     Collection JavaDoc candidates = getRemovalCandidatesInternal(maxCount);
105     if (config.isDebugEnabled()) {
106       reportTime("Cache", cache.subList(0, cache.size()));
107       reportTime("Eviction candidates", candidates);
108     }
109     return candidates;
110   }
111
112   private void reportTime(String JavaDoc name, Collection JavaDoc cacheables) {
113     long now = System.currentTimeMillis();
114     long times[] = new long[cacheables.size()];
115     int j = 0;
116     long avg = 0;
117     synchronized (this) {
118       for (Iterator JavaDoc i = cacheables.iterator(); i.hasNext();) {
119         Cacheable c = (Cacheable) i.next();
120         long diff = now - smap.get(c.getObjectID());
121         times[j++] = diff;
122         avg += diff;
123       }
124     }
125     avg = avg / times.length;
126     // Stupid but whatever
127
Arrays.sort(times);
128     StringBuffer JavaDoc sb = new StringBuffer JavaDoc(name);
129     sb.append(" : size = ").append(cacheables.size()).append(" Avg = ").append(avg);
130     sb.append(" Min = ").append(times[0]);
131     sb.append(" Max = ").append(times[times.length - 1]);
132     sb.append("\n\n");
133     int n10 = times.length / 10;
134     for (int i = 1; i < 10; i++) {
135       sb.append("\t").append(i * 10).append(" % = ").append(times[n10 * i]).append("\n");
136     }
137     sb.append("\n\n");
138     logger.info(sb.toString());
139   }
140
141   private Collection JavaDoc getRemovalCandidatesInternal(int maxCount) {
142
143     long start = System.currentTimeMillis();
144     Collection JavaDoc rv = new HashSet JavaDoc();
145     int count = 0;
146     ArrayList JavaDoc accessCounts;
147     Cacheable stop, c;
148     synchronized (this) {
149       if (capacity > 0) {
150         if (!isCacheFull()) { return Collections.EMPTY_LIST; }
151         if (maxCount <= 0 || maxCount > evictionSize) {
152           maxCount = evictionSize;
153         }
154       } else if (maxCount <= 0) {
155         // disallow negetative maxCount when capacity is negative
156
throw new AssertionError JavaDoc("Please specify maxcount > 0 as capacity is set to : " + capacity + " Max Count = "
157                                  + maxCount);
158       }
159
160       count = Math.min(cache.size(), maxCount);
161       accessCounts = new ArrayList JavaDoc(cache.size());
162
163       c = (Cacheable) cache.getFirst();
164       stop = getStopPoint();
165       int agingFactor = (int) config.getAgingFactor();
166       // Step 1: Remove elements which were never accessed and at the same time collect stats
167
while (cache.size() - rv.size() > capacity && count > 0 && c != null && c != stop) {
168         Cacheable next = (Cacheable) c.getNext();
169         int accessed = c.accessCount(agingFactor);
170         if (accessed == 0) {
171           if (c.canEvict()) {
172             rv.add(c);
173             cache.remove(c);
174             cache.addLast(c);
175             count--;
176           }
177         } else {
178           // incrementAccessCountFor(accessCountSummary, accessed);
179
accessCounts.add(new Integer JavaDoc(accessed));
180
181         }
182         c = next;
183       }
184       while (c != null && c != stop) {
185         c.accessCount(agingFactor);
186         c = (Cacheable) c.getNext();
187       }
188       if (cache.size() - rv.size() <= capacity || count <= 0) {
189         // we already got what is needed
190
log_time_taken(start);
191         return rv;
192       }
193     }
194
195     // Step 2: Do the sorting ... This can be optimized since we dont need it to be sorted.
196
Map accessCountSummary = new TreeMap JavaDoc(); // This is sorted map
197
for (Iterator JavaDoc i = accessCounts.iterator(); i.hasNext();) {
198       Integer JavaDoc ac = (Integer JavaDoc) i.next();
199       incrementAccessCountFor(accessCountSummary, ac);
200     }
201
202     // release memory when done
203
accessCounts = null;
204
205     // Step 3: Use the summary that was built earlier to decide the accessCountCutOff
206
int accessCountCutOff = 0;
207     int remaining = count;
208     for (Iterator JavaDoc i = accessCountSummary.entrySet().iterator(); i.hasNext();) {
209       Entry e = (Entry) i.next();
210       accessCountCutOff = ((Integer JavaDoc) e.getKey()).intValue();
211       int occurance = ((Integer JavaDoc) e.getValue()).intValue();
212       remaining -= occurance;
213       if (remaining <= 0) {
214         break;
215       }
216     }
217
218     // release memory when done
219
accessCountSummary = null;
220
221     // Step 4 : Use the calculated accessCountCutOff to get the rigth candidates under the lock. Since we release teh
222
// lock,
223
// we have to be fault tolerant
224
synchronized (this) {
225       c = (Cacheable) cache.getFirst();
226       while (cache.size() - rv.size() > capacity && count > 0 && c != null && c != stop) {
227         Cacheable next = (Cacheable) c.getNext();
228         int accessed = c.accessCount(1);
229         if (accessed <= accessCountCutOff) {
230           if (c.canEvict()) {
231             rv.add(c);
232             cache.remove(c);
233             cache.addLast(c);
234             count--;
235           }
236         }
237         c = next;
238       }
239       log_time_taken(start);
240       return rv;
241     }
242   }
243
244   private Cacheable getStopPoint() {
245     // The last LRU_IN_MEMORY_PERCENTAGE of element are not processed to be fair with new objects/recently accessed
246
// objects
247
Cacheable stop = (Cacheable) cache.getLast();
248     int ignore = (int) (cache.size() * config.getRecentlyAccessedIgnorePercentage() / 100.0); // force floating point
249
// arithemetic
250
while (ignore-- > 0) {
251       stop = (Cacheable) stop.getPrevious();
252     }
253     return stop;
254   }
255
256   private void log_time_taken(long start) {
257     long taken = System.currentTimeMillis() - start;
258     if (taken > 1000) {
259       logger.info("Time taken to compute removal candidates : " + taken + " ms");
260     }
261   }
262
263   private void incrementAccessCountFor(Map accessCountSummary, Integer JavaDoc key) {
264     Integer JavaDoc count = (Integer JavaDoc) accessCountSummary.get(key);
265     if (count == null) {
266       accessCountSummary.put(key, new Integer JavaDoc(1));
267     } else {
268       accessCountSummary.put(key, new Integer JavaDoc(count.intValue() + 1));
269     }
270   }
271
272   private boolean contains(Cacheable obj) {
273     // XXX: This is here to get around bogus implementation of TLinkedList.contains(Object)
274
return obj != null && (obj.getNext() != null || obj.getPrevious() != null);
275   }
276
277   public synchronized void remove(Cacheable obj) {
278     if (contains(obj)) {
279       cache.remove(obj);
280       if (config.isDebugEnabled()) smap.remove(obj.getObjectID());
281     }
282   }
283
284   public PrettyPrinter prettyPrint(PrettyPrinter out) {
285     PrettyPrinter rv = out;
286     out.println(getClass().getName());
287     out = out.duplicateAndIndent();
288     out.indent().println("max size: " + capacity).indent().print("cache: ").visit(cache).println();
289     return rv;
290   }
291
292 }
293
Popular Tags