View Javadoc

1   /***
2    *  Copyright 2003-2010 Terracotta, Inc.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache.pool.impl;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.Comparator;
23  import java.util.List;
24  
25  import net.sf.ehcache.pool.PoolEvictor;
26  
27  /***
28   * Abstract implementation of a global 'cache value' maximizing pool eviction algorithm.
29   * <p>
30   *
31   * @author Chris Dennis
32   *
33   * @param <T> type of store handled by this evictor
34   */
35  public abstract class AbstractBalancedAccessEvictor<T> implements PoolEvictor<T> {
36  
37      private static final double ALPHA = 1.0;
38  
39      /***
40       * Comparator used to rank the stores in order of eviction 'cost'.
41       */
42      private class EvictionCostComparator implements Comparator<T> {
43  
44          private final long unloadedSize;
45  
46          public EvictionCostComparator(long unloadedSize) {
47              this.unloadedSize = unloadedSize;
48          }
49  
50          public int compare(T s1, T s2) {
51              return Float.compare(evictionCost(s1, unloadedSize), evictionCost(s2, unloadedSize));
52          }
53      }
54  
55      /***
56       * Evict the specified number of bytes or the hinted number of elements from the specified store
57       *
58       * @param store store to evict from
59       * @param count number of elements to evict
60       * @param bytes number of bytes to evict
61       * @return {@code true} if the eviction succeeded
62       */
63      protected abstract boolean evict(T store, int count, long bytes);
64  
65      /***
66       * Return the hit rate for the supplied store.
67       *
68       * @param store store to query
69       * @return hit rate
70       */
71      protected abstract float hitRate(T store);
72  
73      /***
74       * Return the miss rate for the supplied store.
75       *
76       * @param store store to query
77       * @return miss rate
78       */
79      protected abstract float missRate(T store);
80  
81      /***
82       * Return the number of mappings in the supplied store.
83       *
84       * @param store store to size
85       * @return mapping count
86       */
87      protected abstract long countSize(T store);
88  
89      /***
90       * Return the size in bytes of the supplied store.
91       *
92       * @param store store to size
93       * @return size in bytes
94       */
95      protected abstract long byteSize(T store);
96  
97      /***
98       * {@inheritDoc}
99       */
100     public boolean freeSpace(Collection<T> from, long bytes) {
101         if (from == null || from.isEmpty()) {
102             return false;
103         }
104         List<T> sorted = new ArrayList<T>(from);
105         Collections.sort(sorted, new EvictionCostComparator(getDesiredUnloadedSize(from)));
106 
107         for (T store : sorted) {
108             int count;
109             long byteSize = byteSize(store);
110             long countSize = countSize(store);
111             if (countSize == 0 || byteSize == 0) {
112                 count = 1;
113             } else {
114                 count = (int) Math.max((bytes * countSize) / byteSize, 1L);
115             }
116             if (evict(store, count, bytes)) {
117                 return true;
118             }
119         }
120         return false;
121     }
122 
123     private float evictionCost(T store, long unloadedSize) {
124         /*
125          * The code below is a simplified version of this:
126          *
127          * float meanEntrySize = byteSize / countSize;
128          * float accessRate = hitRate + missRate;
129          * float fillLevel = hitRate / accessRate;
130          * float deltaFillLevel = fillLevel / byteSize;
131          *
132          * return meanEntrySize * accessRate * deltaFillLevel * hitDistributionFunction(fillLevel);
133          */
134 
135         float hitRate = hitRate(store);
136         float missRate = missRate(store);
137         long countSize = countSize(store);
138         float accessRate = hitRate + missRate;
139 
140         if (accessRate == 0.0f) {
141             if (byteSize(store) > unloadedSize) {
142                 return Float.NEGATIVE_INFINITY;
143             } else {
144                 return Float.POSITIVE_INFINITY;
145             }
146         } else if (hitRate == 0.0f) {
147             return Float.POSITIVE_INFINITY;
148         } else {
149             float cost = (hitRate / countSize) * hitDistributionFunction(hitRate / accessRate);
150             if (Float.isNaN(cost)) {
151                 throw new AssertionError(String.format("NaN Eviction Cost [hit:%f miss:%f size:%d]", hitRate, missRate, countSize));
152             } else {
153                 return cost;
154             }
155         }
156     }
157 
158     private static float hitDistributionFunction(float fillLevel) {
159         return (float) Math.pow(fillLevel, -ALPHA);
160     }
161 
162     private long getDesiredUnloadedSize(Collection<T> from) {
163         long unloadedSize = 0L;
164         for (T s : from) {
165             unloadedSize += byteSize(s);
166         }
167         return unloadedSize / from.size();
168     }
169 }