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
126
127
128
129
130
131
132
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 }