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

LIRS replacement algorithm

上级 95940109
...@@ -6,10 +6,13 @@ ...@@ -6,10 +6,13 @@
package org.h2.test.store; package org.h2.test.store;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Random; import java.util.Random;
import org.h2.dev.store.btree.CacheLirs; import org.h2.dev.store.btree.CacheLirs;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.upgrade.v1_1.util.Profiler;
import org.h2.util.New; import org.h2.util.New;
/** /**
...@@ -27,14 +30,19 @@ public class TestCache extends TestBase { ...@@ -27,14 +30,19 @@ public class TestCache extends TestBase {
} }
public void test() throws Exception { public void test() throws Exception {
Profiler p = new Profiler();
p.startCollecting();
testEdgeCases(); testEdgeCases();
testSize();
testClear(); testClear();
testGetPutPeekRemove(); testGetPutPeekRemove();
testPruneStack();
testLimitHot(); testLimitHot();
testLimitNonResident(); testLimitNonResident();
testBadHashMethod(); testBadHashMethod();
testScanResistance(); testScanResistance();
testRandomOperations(); testRandomOperations();
System.out.println(p.getTop(5));
} }
private void testEdgeCases() { private void testEdgeCases() {
...@@ -67,6 +75,39 @@ public class TestCache extends TestBase { ...@@ -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() { private void testGetPutPeekRemove() {
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(4, 1); CacheLirs<Integer, Integer> test = CacheLirs.newInstance(4, 1);
test.put(1, 10); test.put(1, 10);
...@@ -90,6 +131,10 @@ public class TestCache extends TestBase { ...@@ -90,6 +131,10 @@ public class TestCache extends TestBase {
// 5 is cold; will make 4 non-resident // 5 is cold; will make 4 non-resident
test.put(5, 50); test.put(5, 50);
verify(test, "mem: 4 stack: 5 3 1 2 cold: 5 non-resident: 4"); 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.peek(4));
assertNull(test.get(4)); assertNull(test.get(4));
assertEquals(10, test.get(1).intValue()); assertEquals(10, test.get(1).intValue());
...@@ -180,6 +225,25 @@ public class TestCache extends TestBase { ...@@ -180,6 +225,25 @@ public class TestCache extends TestBase {
verify(test, "mem: 4 stack: 6 3 4 cold: 2 non-resident: 5 1"); 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() { private void testClear() {
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(40, 10); CacheLirs<Integer, Integer> test = CacheLirs.newInstance(40, 10);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
...@@ -319,6 +383,7 @@ public class TestCache extends TestBase { ...@@ -319,6 +383,7 @@ public class TestCache extends TestBase {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
test.put(-i, -i * 10); test.put(-i, -i * 10);
} }
verify(test, null);
// init with 0..9, ensure those are hot entries // init with 0..9, ensure those are hot entries
for (int i = 0; i < size / 2; i++) { for (int i = 0; i < size / 2; i++) {
test.put(i, i * 10); test.put(i, i * 10);
...@@ -327,6 +392,7 @@ public class TestCache extends TestBase { ...@@ -327,6 +392,7 @@ public class TestCache extends TestBase {
System.out.println("get " + i + " -> " + test); System.out.println("get " + i + " -> " + test);
} }
} }
verify(test, null);
// read 0..9, add 10..19 (cold) // read 0..9, add 10..19 (cold)
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
Integer x = test.get(i); Integer x = test.get(i);
...@@ -346,6 +412,7 @@ public class TestCache extends TestBase { ...@@ -346,6 +412,7 @@ public class TestCache extends TestBase {
if (log) { if (log) {
System.out.println("get " + i + " -> " + test); System.out.println("get " + i + " -> " + test);
} }
verify(test, null);
} }
// ensure 0..9 are hot, 10..18 are not resident, 19 is cold // ensure 0..9 are hot, 10..18 are not resident, 19 is cold
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
...@@ -356,6 +423,7 @@ public class TestCache extends TestBase { ...@@ -356,6 +423,7 @@ public class TestCache extends TestBase {
} else { } else {
assertNull(x); assertNull(x);
} }
verify(test, null);
} }
} }
...@@ -401,6 +469,7 @@ public class TestCache extends TestBase { ...@@ -401,6 +469,7 @@ public class TestCache extends TestBase {
System.out.println(" -> " + toString(test)); System.out.println(" -> " + toString(test));
} }
} }
verify(test, null);
} }
} }
...@@ -423,13 +492,28 @@ public class TestCache extends TestBase { ...@@ -423,13 +492,28 @@ public class TestCache extends TestBase {
} }
private <K, V> void verify(CacheLirs<K, V> cache, String expected) { private <K, V> void verify(CacheLirs<K, V> cache, String expected) {
String got = toString(cache); if (expected != null) {
assertEquals(expected, got); String got = toString(cache);
assertEquals(expected, got);
}
int mem = 0; int mem = 0;
for (K k : cache.keySet()) { for (K k : cache.keySet()) {
mem += cache.getMemory(k); mem += cache.getMemory(k);
} }
assertEquals(mem, cache.getUsedMemory()); 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; ...@@ -15,25 +15,25 @@ import java.util.Map;
import java.util.Set; 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> * <p>
* This implementation is not multi-threading save. Null keys or null values are * 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 * 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> * <p>
* Each each entry is assigned a distinct memory size, and the cache will try to * Each entry is assigned a distinct memory size, and the cache will try to use
* use at most the specified amount of memory. The memory unit is not relevant, * at most the specified amount of memory. The memory unit is not relevant,
* however it is suggested to use bytes as the unit. * however it is suggested to use bytes as the unit.
* <p> * <p>
* An implementation of the LIRS replacement algorithm from Xiaodong Zhang and * This class implements the LIRS replacement algorithm invented by Xiaodong
* Song Jiang as described in * Zhang and Song Jiang as described in
* http://www.cse.ohio-state.edu/~zhang/lirs-sigmetrics-02.html with a few * 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 * 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 * 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 * @author Thomas Mueller
*
* @param <K> the key type * @param <K> the key type
* @param <V> the value type * @param <V> the value type
*/ */
...@@ -55,12 +55,12 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -55,12 +55,12 @@ public class CacheLirs<K, V> implements Map<K, V> {
private long usedMemory; 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; private int mapSize;
/** /**
* The LIRS stack size. This includes all hot and some of the cold entries. * The LIRS stack size.
*/ */
private int stackSize; private int stackSize;
...@@ -75,13 +75,13 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -75,13 +75,13 @@ public class CacheLirs<K, V> implements Map<K, V> {
private int queue2Size; 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; private Entry<K, V>[] entries;
/** /**
* The bit mask that is applied to the key hash code to get the map index. * The bit mask that is applied to the key hash code to get the index in the
* The value is the size of the entries array minus one. * map array. The mask is the length of the array minus one.
*/ */
private int mask; private int mask;
...@@ -114,7 +114,9 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -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 maxMemory the maximum memory to use (1 or larger)
* @param averageMemory the average memory (1 or larger) * @param averageMemory the average memory (1 or larger)
...@@ -125,12 +127,13 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -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() { public void clear() {
// calculate the size of the map array // 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); long maxLen = (long) (maxMemory / averageMemory / 0.75);
// the size needs to be a power of 2 // the size needs to be a power of 2
long l = 8; long l = 8;
...@@ -162,34 +165,35 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -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 * Get the value for the given key if the entry is cached. This method does
* internal state. * not modify the internal state.
* *
* @param key the key * @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 peek(K key) { public V peek(K key) {
Entry<K, V> e = find(key); Entry<K, V> e = find(key);
return e == null ? null : e.value; return e == null ? null : e.value;
} }
/** /**
* Get the memory used for the given key. * 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 * @return the memory, or 0 if there is no resident entry
*/ */
public int getMemory(K key) { public int getMemory(K key) {
Entry<K, V> e = find(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 * Get the value for the given key if the entry is cached. This method
* state of the cache, to ensure commonly used entries stay in the cache. * adjusts the internal state of the cache, to ensure commonly used entries
* stay in the cache.
* *
* @param key the key (may not be null) * @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) { public V get(Object key) {
Entry<K, V> e = find(key); Entry<K, V> e = find(key);
...@@ -229,8 +233,7 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -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 * Add an entry to the cache using the average memory size.
* with the average memory size.
* *
* @param key the key (may not be null) * @param key the key (may not be null)
* @param value the value (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> { ...@@ -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) * @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) { public V remove(Object key) {
int hash = key.hashCode(); int hash = key.hashCode();
...@@ -450,7 +453,7 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -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 * Get the list of keys. This method allows to view the internal state of
* the cache. * 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 * @param nonResident true for non-resident entries
* @return the key list * @return the key list
*/ */
...@@ -477,10 +480,10 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -477,10 +480,10 @@ public class CacheLirs<K, V> implements Map<K, V> {
public int size() { public int size() {
return mapSize - queue2Size; return mapSize - queue2Size;
} }
/** /**
* Check whether there are any resident entries in the map. * Check whether there are any resident entries in the map.
* *
* @return true if there are no keys * @return true if there are no keys
*/ */
public boolean isEmpty() { public boolean isEmpty() {
...@@ -489,8 +492,8 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -489,8 +492,8 @@ public class CacheLirs<K, V> implements Map<K, V> {
/** /**
* Check whether there is a resident entry for the given key. * 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) { public boolean containsKey(Object key) {
Entry<K, V> e = find(key); Entry<K, V> e = find(key);
...@@ -499,19 +502,30 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -499,19 +502,30 @@ public class CacheLirs<K, V> implements Map<K, V> {
/** /**
* Check whether there are any keys for the given value. * Check whether there are any keys for the given value.
* *
* @return true if there is a key for this value * @return true if there is a key for this value
*/ */
public boolean containsValue(Object value) { public boolean containsValue(Object value) {
return values().contains(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) { public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
put(e.getKey(), e.getValue()); put(e.getKey(), e.getValue());
} }
} }
/**
* Get the set of keys for resident entries.
*
* @return the set of keys
*/
public Set<K> keySet() { public Set<K> keySet() {
HashSet<K> set = new HashSet<K>(); HashSet<K> set = new HashSet<K>();
for (Entry<K, V> e = stack.stackNext; e != stack; e = e.stackNext) { 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> { ...@@ -523,6 +537,11 @@ public class CacheLirs<K, V> implements Map<K, V> {
return set; return set;
} }
/**
* Get the collection of values.
*
* @return the collection of values
*/
public Collection<V> values() { public Collection<V> values() {
ArrayList<V> list = new ArrayList<V>(); ArrayList<V> list = new ArrayList<V>();
for (K k : keySet()) { for (K k : keySet()) {
...@@ -531,6 +550,11 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -531,6 +550,11 @@ public class CacheLirs<K, V> implements Map<K, V> {
return list; return list;
} }
/**
* Get the entry set for all resident entries.
*
* @return the entry set
*/
public Set<Map.Entry<K, V>> entrySet() { public Set<Map.Entry<K, V>> entrySet() {
HashMap<K, V> map = new HashMap<K, V>(); HashMap<K, V> map = new HashMap<K, V>();
for (K k : keySet()) { for (K k : keySet()) {
...@@ -542,7 +566,7 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -542,7 +566,7 @@ public class CacheLirs<K, V> implements Map<K, V> {
/** /**
* Get the number of hot entries in the cache. * Get the number of hot entries in the cache.
* *
* @return the number of entries * @return the number of hot entries
*/ */
public int sizeHot() { public int sizeHot() {
return mapSize - queueSize - queue2Size; return mapSize - queueSize - queue2Size;
...@@ -551,12 +575,21 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -551,12 +575,21 @@ public class CacheLirs<K, V> implements Map<K, V> {
/** /**
* Get the number of non-resident entries in the cache. * 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() { public int sizeNonResident() {
return queue2Size; 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. * Get the currently used memory.
* *
...@@ -568,7 +601,8 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -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 * 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) * @param maxMemory the maximum size (1 or larger)
*/ */
...@@ -589,8 +623,8 @@ public class CacheLirs<K, V> implements Map<K, V> { ...@@ -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 * Set the average memory used per entry. It is used to calculate the length
* of the map. * of the internal array.
* *
* @param averageMemory the average memory used (1 or larger) * @param averageMemory the average memory used (1 or larger)
*/ */
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论