提交 b052ab59 authored 作者: Thomas Mueller's avatar Thomas Mueller

LIRS replacement algorithm

上级 95940109
......@@ -6,10 +6,13 @@
package org.h2.test.store;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;
import org.h2.dev.store.btree.CacheLirs;
import org.h2.test.TestBase;
import org.h2.upgrade.v1_1.util.Profiler;
import org.h2.util.New;
/**
......@@ -27,14 +30,19 @@ public class TestCache extends TestBase {
}
public void test() throws Exception {
Profiler p = new Profiler();
p.startCollecting();
testEdgeCases();
testSize();
testClear();
testGetPutPeekRemove();
testPruneStack();
testLimitHot();
testLimitNonResident();
testBadHashMethod();
testScanResistance();
testRandomOperations();
System.out.println(p.getTop(5));
}
private void testEdgeCases() {
......@@ -67,6 +75,39 @@ public class TestCache extends TestBase {
}
}
private void testSize() {
verifyMapSize(7, 16);
verifyMapSize(13, 32);
verifyMapSize(25, 64);
verifyMapSize(49, 128);
verifyMapSize(97, 256);
verifyMapSize(193, 512);
verifyMapSize(385, 1024);
verifyMapSize(769, 2048);
CacheLirs<Integer, Integer> test;
test = CacheLirs.newInstance(1000, 1);
for (int j = 0; j < 2000; j++) {
test.put(j, j);
}
// for a cache of size 1000,
// there are 62 cold entries (about 6.25%).
assertEquals(62, test.size() - test.sizeHot());
// at most as many non-resident elements
// as there are entries in the stack
assertEquals(968, test.sizeNonResident());
}
private void verifyMapSize(int elements, int mapSize) {
CacheLirs<Integer, Integer> test;
test = CacheLirs.newInstance(elements - 1, 1);
assertTrue(mapSize > test.sizeMapArray());
test = CacheLirs.newInstance(elements, 1);
assertEquals(mapSize, test.sizeMapArray());
test = CacheLirs.newInstance(elements * 100, 100);
assertEquals(mapSize, test.sizeMapArray());
}
private void testGetPutPeekRemove() {
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(4, 1);
test.put(1, 10);
......@@ -90,6 +131,10 @@ public class TestCache extends TestBase {
// 5 is cold; will make 4 non-resident
test.put(5, 50);
verify(test, "mem: 4 stack: 5 3 1 2 cold: 5 non-resident: 4");
assertEquals(1, test.getMemory(1));
assertEquals(1, test.getMemory(5));
assertEquals(0, test.getMemory(4));
assertEquals(0, test.getMemory(100));
assertNull(test.peek(4));
assertNull(test.get(4));
assertEquals(10, test.get(1).intValue());
......@@ -180,6 +225,25 @@ public class TestCache extends TestBase {
verify(test, "mem: 4 stack: 6 3 4 cold: 2 non-resident: 5 1");
}
private void testPruneStack() {
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(5, 1);
for (int i = 0; i < 7; i++) {
test.put(i, i * 10);
}
verify(test, "mem: 5 stack: 6 5 4 3 2 1 cold: 6 non-resident: 5 0");
test.get(4);
test.get(3);
test.get(2);
verify(test, "mem: 5 stack: 2 3 4 6 5 1 cold: 6 non-resident: 5 0");
// this call needs to prune the stack
test.remove(1);
verify(test, "mem: 4 stack: 2 3 4 6 cold: non-resident: 5 0");
test.put(0, 0);
test.put(1, 10);
// the the stack was not pruned, the following will fail
verify(test, "mem: 5 stack: 1 0 2 3 4 cold: 1 non-resident: 6 5");
}
private void testClear() {
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(40, 10);
for (int i = 0; i < 5; i++) {
......@@ -319,6 +383,7 @@ public class TestCache extends TestBase {
for (int i = 0; i < size; i++) {
test.put(-i, -i * 10);
}
verify(test, null);
// init with 0..9, ensure those are hot entries
for (int i = 0; i < size / 2; i++) {
test.put(i, i * 10);
......@@ -327,6 +392,7 @@ public class TestCache extends TestBase {
System.out.println("get " + i + " -> " + test);
}
}
verify(test, null);
// read 0..9, add 10..19 (cold)
for (int i = 0; i < size; i++) {
Integer x = test.get(i);
......@@ -346,6 +412,7 @@ public class TestCache extends TestBase {
if (log) {
System.out.println("get " + i + " -> " + test);
}
verify(test, null);
}
// ensure 0..9 are hot, 10..18 are not resident, 19 is cold
for (int i = 0; i < size; i++) {
......@@ -356,6 +423,7 @@ public class TestCache extends TestBase {
} else {
assertNull(x);
}
verify(test, null);
}
}
......@@ -401,6 +469,7 @@ public class TestCache extends TestBase {
System.out.println(" -> " + toString(test));
}
}
verify(test, null);
}
}
......@@ -423,13 +492,28 @@ public class TestCache extends TestBase {
}
private <K, V> void verify(CacheLirs<K, V> cache, String expected) {
String got = toString(cache);
assertEquals(expected, got);
if (expected != null) {
String got = toString(cache);
assertEquals(expected, got);
}
int mem = 0;
for (K k : cache.keySet()) {
mem += cache.getMemory(k);
}
assertEquals(mem, cache.getUsedMemory());
List<K> stack = cache.keys(false, false);
List<K> cold = cache.keys(true, false);
List<K> nonResident = cache.keys(true, true);
assertEquals(nonResident.size(), cache.sizeNonResident());
HashSet<K> hot = new HashSet<K>(stack);
hot.removeAll(cold);
hot.removeAll(nonResident);
assertEquals(hot.size(), cache.sizeHot());
assertEquals(hot.size() + cold.size(), cache.size());
if (stack.size() > 0) {
K lastStack = stack.get(stack.size() - 1);
assertTrue(hot.contains(lastStack));
}
}
}
......@@ -15,25 +15,25 @@ import java.util.Map;
import java.util.Set;
/**
* A LIRS cache.
* A scan resistent cache. It is meant to cache objects that are relatively
* costly to acquire, for example file content.
* <p>
* This implementation is not multi-threading save. Null keys or null values are
* not allowed. There is no guard against bad hash functions, so it is important
* to the hash function of the key is good.
* to the hash function of the key is good. The map fill factor is at most 75%.
* <p>
* Each each entry is assigned a distinct memory size, and the cache will try to
* use at most the specified amount of memory. The memory unit is not relevant,
* Each entry is assigned a distinct memory size, and the cache will try to use
* at most the specified amount of memory. The memory unit is not relevant,
* however it is suggested to use bytes as the unit.
* <p>
* An implementation of the LIRS replacement algorithm from Xiaodong Zhang and
* Song Jiang as described in
* This class implements the LIRS replacement algorithm invented by Xiaodong
* Zhang and Song Jiang as described in
* http://www.cse.ohio-state.edu/~zhang/lirs-sigmetrics-02.html with a few
* smaller changes: An additional queue for non-resident entries is used, to
* prevent unbound memory usage. The maximum size of this queue is at most the
* size of the rest of the stack. About 5% of the mapped entries are cold.
* size of the rest of the stack. About 6.25% of the mapped entries are cold.
*
* @author Thomas Mueller
*
* @param <K> the key type
* @param <V> the value type
*/
......@@ -55,12 +55,12 @@ public class CacheLirs<K, V> implements Map<K, V> {
private long usedMemory;
/**
* The number of entries in the map. This includes all hot and cold entries.
* The number of (hot, cold, and non-resident) entries in the map.
*/
private int mapSize;
/**
* The LIRS stack size. This includes all hot and some of the cold entries.
* The LIRS stack size.
*/
private int stackSize;
......@@ -75,13 +75,13 @@ public class CacheLirs<K, V> implements Map<K, V> {
private int queue2Size;
/**
* The map entries. The size is always a power of 2.
* The map array. The size is always a power of 2.
*/
private Entry<K, V>[] entries;
/**
* The bit mask that is applied to the key hash code to get the map index.
* The value is the size of the entries array minus one.
* The bit mask that is applied to the key hash code to get the index in the
* map array. The mask is the length of the array minus one.
*/
private int mask;
......@@ -114,7 +114,9 @@ public class CacheLirs<K, V> implements Map<K, V> {
}
/**
* Create a new cache with the given size in number of entries.
* Create a new cache with the given memory size. To just limit the number
* of entries, use the required number as the maximum memory, and an average
* size of 1.
*
* @param maxMemory the maximum memory to use (1 or larger)
* @param averageMemory the average memory (1 or larger)
......@@ -125,12 +127,13 @@ public class CacheLirs<K, V> implements Map<K, V> {
}
/**
* Clear the cache.
*/
* Clear the cache. This method will clear all entries (including
* non-resident keys) and resize the internal array.
**/
public void clear() {
// calculate the size of the map array
// assume a fill factor of at most 75%
// assume a fill factor of at most 80%
long maxLen = (long) (maxMemory / averageMemory / 0.75);
// the size needs to be a power of 2
long l = 8;
......@@ -162,34 +165,35 @@ public class CacheLirs<K, V> implements Map<K, V> {
}
/**
* Get an entry if the entry is cached. This method does not modify the
* internal state.
* Get the value for the given key if the entry is cached. This method does
* not modify the internal state.
*
* @param key the key
* @return the value, or null if not found
* @param key the key (may not be null)
* @return the value, or null if there is no resident entry
*/
public V peek(K key) {
Entry<K, V> e = find(key);
return e == null ? null : e.value;
}
/**
* Get the memory used for the given key.
*
* @param key the key
*
* @param key the key (may not be null)
* @return the memory, or 0 if there is no resident entry
*/
public int getMemory(K key) {
Entry<K, V> e = find(key);
return e == null ? null : e.memory;
return e == null ? 0 : e.memory;
}
/**
* Get an entry if the entry is cached. This method adjusts the internal
* state of the cache, to ensure commonly used entries stay in the cache.
* Get the value for the given key if the entry is cached. This method
* adjusts the internal state of the cache, to ensure commonly used entries
* stay in the cache.
*
* @param key the key (may not be null)
* @return the value, or null if not found
* @return the value, or null if there is no resident entry
*/
public V get(Object key) {
Entry<K, V> e = find(key);
......@@ -229,8 +233,7 @@ public class CacheLirs<K, V> implements Map<K, V> {
}
/**
* Add an entry to the cache. This method is the same as adding an entry
* with the average memory size.
* Add an entry to the cache using the average memory size.
*
* @param key the key (may not be null)
* @param value the value (may not be null)
......@@ -279,10 +282,10 @@ public class CacheLirs<K, V> implements Map<K, V> {
}
/**
* Remove an entry.
* Remove an entry. Both resident and non-resident entries can be removed.
*
* @param key the key (may not be null)
* @return true if the entry was found (resident or non-resident)
* @return the old value, or null if there is no resident entry
*/
public V remove(Object key) {
int hash = key.hashCode();
......@@ -450,7 +453,7 @@ public class CacheLirs<K, V> implements Map<K, V> {
* Get the list of keys. This method allows to view the internal state of
* the cache.
*
* @param cold if true, the keys for the cold entries are returned
* @param cold if true, only keys for the cold entries are returned
* @param nonResident true for non-resident entries
* @return the key list
*/
......@@ -477,10 +480,10 @@ public class CacheLirs<K, V> implements Map<K, V> {
public int size() {
return mapSize - queue2Size;
}
/**
* Check whether there are any resident entries in the map.
*
*
* @return true if there are no keys
*/
public boolean isEmpty() {
......@@ -489,8 +492,8 @@ public class CacheLirs<K, V> implements Map<K, V> {
/**
* Check whether there is a resident entry for the given key.
*
* @return true if the key is in the map
*
* @return true if there is a resident entry
*/
public boolean containsKey(Object key) {
Entry<K, V> e = find(key);
......@@ -499,19 +502,30 @@ public class CacheLirs<K, V> implements Map<K, V> {
/**
* Check whether there are any keys for the given value.
*
*
* @return true if there is a key for this value
*/
public boolean containsValue(Object value) {
return values().contains(value);
}
/**
* Add all entries of the given map to this map. This method will use the
* average memory size.
*
* @param m the source map
*/
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
/**
* Get the set of keys for resident entries.
*
* @return the set of keys
*/
public Set<K> keySet() {
HashSet<K> set = new HashSet<K>();
for (Entry<K, V> e = stack.stackNext; e != stack; e = e.stackNext) {
......@@ -523,6 +537,11 @@ public class CacheLirs<K, V> implements Map<K, V> {
return set;
}
/**
* Get the collection of values.
*
* @return the collection of values
*/
public Collection<V> values() {
ArrayList<V> list = new ArrayList<V>();
for (K k : keySet()) {
......@@ -531,6 +550,11 @@ public class CacheLirs<K, V> implements Map<K, V> {
return list;
}
/**
* Get the entry set for all resident entries.
*
* @return the entry set
*/
public Set<Map.Entry<K, V>> entrySet() {
HashMap<K, V> map = new HashMap<K, V>();
for (K k : keySet()) {
......@@ -542,7 +566,7 @@ public class CacheLirs<K, V> implements Map<K, V> {
/**
* Get the number of hot entries in the cache.
*
* @return the number of entries
* @return the number of hot entries
*/
public int sizeHot() {
return mapSize - queueSize - queue2Size;
......@@ -551,12 +575,21 @@ public class CacheLirs<K, V> implements Map<K, V> {
/**
* Get the number of non-resident entries in the cache.
*
* @return the number of entries
* @return the number of non-resident entries
*/
public int sizeNonResident() {
return queue2Size;
}
/**
* Get the length of the internal map array.
*
* @return the size of the array
*/
public int sizeMapArray() {
return entries.length;
}
/**
* Get the currently used memory.
*
......@@ -568,7 +601,8 @@ public class CacheLirs<K, V> implements Map<K, V> {
/**
* Set the maximum memory this cache should use. This will not immediately
* cause entries to get removed however; it will only change the limit.
* cause entries to get removed however; it will only change the limit. To
* resize the internal array, call the clear method.
*
* @param maxMemory the maximum size (1 or larger)
*/
......@@ -589,8 +623,8 @@ public class CacheLirs<K, V> implements Map<K, V> {
}
/**
* Set the average memory used per entry. It is used to calculate the size
* of the map.
* Set the average memory used per entry. It is used to calculate the length
* of the internal array.
*
* @param averageMemory the average memory used (1 or larger)
*/
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论