2
* Copyright 2003-2010 Terracotta, Inc.
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
8
* http://www.apache.org/licenses/LICENSE-2.0
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.
17
package net.sf.ehcache.pool.impl;
19
import java.util.ArrayList;
20
import java.util.Collection;
21
import java.util.Collections;
22
import java.util.Comparator;
23
import java.util.IdentityHashMap;
24
import java.util.List;
27
import net.sf.ehcache.pool.PoolEvictor;
30
* Abstract implementation of a global 'cache value' maximizing pool eviction algorithm.
33
* @author Chris Dennis
35
* @param <T> type of store handled by this evictor
37
public abstract class AbstractBalancedAccessEvictor<T> implements PoolEvictor<T> {
39
private static final double ALPHA = 1.0;
40
private static final int SAMPLE_SIZE = 5;
43
* Comparator used to rank the stores in order of eviction 'cost'.
45
private final class EvictionCostComparator implements Comparator<T> {
47
private final long unloadedSize;
48
private final Map<T, Float> evictionCostCache;
50
public EvictionCostComparator(long unloadedSize, int collectionSize) {
51
this.unloadedSize = unloadedSize;
52
this.evictionCostCache = new IdentityHashMap<T, Float>(collectionSize);
55
public int compare(T s1, T s2) {
56
Float f1 = evictionCostCache.get(s1);
58
f1 = evictionCost(s1, unloadedSize);
59
evictionCostCache.put(s1, f1);
61
Float f2 = evictionCostCache.get(s2);
63
f2 = evictionCost(s2, unloadedSize);
64
evictionCostCache.put(s2, f2);
66
return Float.compare(f1, f2);
71
* Evict the specified number of bytes or the hinted number of elements from the specified store
73
* @param store store to evict from
74
* @param count number of elements to evict
75
* @param bytes number of bytes to evict
76
* @return {@code true} if the eviction succeeded
78
protected abstract boolean evict(T store, int count, long bytes);
81
* Return the hit rate for the supplied store.
83
* @param store store to query
86
protected abstract float hitRate(T store);
89
* Return the miss rate for the supplied store.
91
* @param store store to query
94
protected abstract float missRate(T store);
97
* Return the number of mappings in the supplied store.
99
* @param store store to size
100
* @return mapping count
102
protected abstract long countSize(T store);
105
* Return the size in bytes of the supplied store.
107
* @param store store to size
108
* @return size in bytes
110
protected abstract long byteSize(T store);
115
public boolean freeSpace(Collection<T> from, long bytes) {
116
if (from == null || from.isEmpty()) {
119
List<T> random = new ArrayList<T>(from);
120
Collections.shuffle(random);
122
for (int i = 0; i < random.size(); i += SAMPLE_SIZE) {
123
List<T> sorted = random.subList(i, Math.min(SAMPLE_SIZE + i, random.size()));
124
Collections.sort(sorted, new EvictionCostComparator(getDesiredUnloadedSize(sorted), sorted.size() + 1));
126
for (T store : sorted) {
128
long byteSize = byteSize(store);
129
long countSize = countSize(store);
130
if (countSize == 0 || byteSize == 0) {
133
count = (int) Math.max((bytes * countSize) / byteSize, 1L);
135
if (evict(store, count, bytes)) {
144
private float evictionCost(T store, long unloadedSize) {
146
* The code below is a simplified version of this:
148
* float meanEntrySize = byteSize / countSize;
149
* float accessRate = hitRate + missRate;
150
* float fillLevel = hitRate / accessRate;
151
* float deltaFillLevel = fillLevel / byteSize;
153
* return meanEntrySize * accessRate * deltaFillLevel * hitDistributionFunction(fillLevel);
156
float hitRate = hitRate(store);
157
float missRate = missRate(store);
158
long countSize = countSize(store);
159
float accessRate = hitRate + missRate;
161
if (accessRate == 0.0f) {
162
if (byteSize(store) > unloadedSize) {
163
return Float.NEGATIVE_INFINITY;
165
return Float.POSITIVE_INFINITY;
167
} else if (hitRate == 0.0f) {
168
return Float.POSITIVE_INFINITY;
170
float cost = (hitRate / countSize) * hitDistributionFunction(hitRate / accessRate);
171
if (Float.isNaN(cost)) {
172
throw new AssertionError(String.format("NaN Eviction Cost [hit:%f miss:%f size:%d]", hitRate, missRate, countSize));
179
private static float hitDistributionFunction(float fillLevel) {
180
return (float) Math.pow(fillLevel, -ALPHA);
183
private long getDesiredUnloadedSize(Collection<T> from) {
184
long unloadedSize = 0L;
186
unloadedSize += byteSize(s);
188
return unloadedSize / from.size();