提交 8a5b3ca8 authored 作者: Andrei Tokar's avatar Andrei Tokar

use weak references in LIRS cache

上级 0494963b
......@@ -5,6 +5,7 @@
*/
package org.h2.mvstore.cache;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
......@@ -123,7 +124,7 @@ public class CacheLongKeyLIRS<V> {
*/
public V peek(long key) {
Entry<V> e = find(key);
return e == null ? null : e.value;
return e == null ? null : e.getValue();
}
/**
......@@ -246,7 +247,7 @@ public class CacheLongKeyLIRS<V> {
* @param key the key
* @return the hash code
*/
static int getHash(long key) {
private static int getHash(long key) {
int hash = (int) ((key >>> 32) ^ key);
// a supplemental secondary hash function
// to protect against hash codes that don't differ much
......@@ -306,7 +307,10 @@ public class CacheLongKeyLIRS<V> {
public synchronized Set<Map.Entry<Long, V>> entrySet() {
HashMap<Long, V> map = new HashMap<>();
for (long k : keySet()) {
map.put(k, find(k).value);
V value = peek(k);
if (value != null) {
map.put(k, value);
}
}
return map.entrySet();
}
......@@ -426,7 +430,7 @@ public class CacheLongKeyLIRS<V> {
public List<V> values() {
ArrayList<V> list = new ArrayList<>();
for (long k : keySet()) {
V value = find(k).value;
V value = peek(k);
if (value != null) {
list.add(value);
}
......@@ -449,7 +453,7 @@ public class CacheLongKeyLIRS<V> {
* @param value the value
* @return true if it is stored
*/
public boolean containsValue(Object value) {
public boolean containsValue(V value) {
return getMap().containsValue(value);
}
......@@ -461,7 +465,7 @@ public class CacheLongKeyLIRS<V> {
public Map<Long, V> getMap() {
HashMap<Long, V> map = new HashMap<>();
for (long k : keySet()) {
V x = find(k).value;
V x = peek(k);
if (x != null) {
map.put(k, x);
}
......@@ -674,15 +678,15 @@ public class CacheLongKeyLIRS<V> {
int index = getHash(e.key) & mask;
e.mapNext = entries[index];
entries[index] = e;
usedMemory += e.memory;
usedMemory += e.getMemory();
mapSize++;
}
private static <V> Entry<V> copy(Entry<V> old) {
Entry<V> e = new Entry<>();
Entry<V> e = new Entry<>(old.memory);
e.key = old.key;
e.value = old.value;
e.memory = old.memory;
e.reference = old.reference;
e.topMove = old.topMove;
return e;
}
......@@ -696,7 +700,7 @@ public class CacheLongKeyLIRS<V> {
*/
int getMemory(long key, int hash) {
Entry<V> e = find(key, hash);
return e == null ? 0 : e.memory;
return e == null ? 0 : e.getMemory();
}
/**
......@@ -710,44 +714,33 @@ public class CacheLongKeyLIRS<V> {
*/
V get(long key, int hash) {
Entry<V> e = find(key, hash);
synchronized (this) {
if (e == null) {
// the entry was not found
misses++;
return null;
}
V value = e.value;
V value = e.getValue();
if (value == null) {
// it was a non-resident entry
misses++;
return null;
}
if (e.isHot()) {
if (e != stack.stackNext) {
if (stackMoveDistance == 0 ||
stackMoveCounter - e.topMove > stackMoveDistance) {
access(key, hash);
}
}
} else {
access(key, hash);
}
access(e);
hits++;
return value;
}
}
/**
* Access an item, moving the entry to the top of the stack or front of
* the queue if found.
*
* @param key the key
* @param e entry to record access for
*/
private synchronized void access(long key, int hash) {
Entry<V> e = find(key, hash);
if (e == null || e.value == null) {
return;
}
private void access(Entry<V> e) {
if (e.isHot()) {
if (e != stack.stackNext) {
if (e != stack.stackNext && e.stackNext != null) {
if (stackMoveDistance == 0 ||
stackMoveCounter - e.topMove > stackMoveDistance) {
// move a hot entry to the top of the stack
......@@ -763,10 +756,17 @@ public class CacheLongKeyLIRS<V> {
}
}
} else {
V v = e.getValue();
if (v != null) {
removeFromQueue(e);
if (e.reference != null) {
e.value = v;
e.reference = null;
usedMemory += e.memory;
}
if (e.stackNext != null) {
// resident cold entries become hot
// if they are on the stack
// resident, or even non-resident (weak value reference),
// cold entries become hot if they are on the stack
removeFromStack(e);
// which means a hot entry needs to become cold
// (this entry is cold, that means there is at least one
......@@ -779,6 +779,10 @@ public class CacheLongKeyLIRS<V> {
}
// in any case, the cold entry is moved to the top of the stack
addToStack(e);
// but if newly promoted cold/non-resident is the only entry on a stack now
// that means last one is cold, need to prune
pruneStack();
}
}
}
......@@ -798,25 +802,20 @@ public class CacheLongKeyLIRS<V> {
throw DataUtils.newIllegalArgumentException(
"The value may not be null");
}
V old;
Entry<V> e = find(key, hash);
boolean existed;
if (e == null) {
existed = false;
old = null;
} else {
existed = true;
old = e.value;
boolean existed = e != null;
V old = null;
if (existed) {
old = e.getValue();
remove(key, hash);
}
if (memory > maxMemory) {
// the new entry is too big to fit
return old;
}
e = new Entry<>();
e = new Entry<>(memory);
e.key = key;
e.value = value;
e.memory = memory;
int index = hash & mask;
e.mapNext = entries[index];
entries[index] = e;
......@@ -836,7 +835,7 @@ public class CacheLongKeyLIRS<V> {
addToStack(e);
if (existed) {
// if it was there before (even non-resident), it becomes hot
access(key, hash);
access(e);
}
return old;
}
......@@ -855,9 +854,7 @@ public class CacheLongKeyLIRS<V> {
if (e == null) {
return null;
}
V old;
if (e.key == key) {
old = e.value;
entries[index] = e.mapNext;
} else {
Entry<V> last;
......@@ -868,11 +865,11 @@ public class CacheLongKeyLIRS<V> {
return null;
}
} while (e.key != key);
old = e.value;
last.mapNext = e.mapNext;
}
V old = e.getValue();
mapSize--;
usedMemory -= e.memory;
usedMemory -= e.getMemory();
if (e.stackNext != null) {
removeFromStack(e);
}
......@@ -886,10 +883,10 @@ public class CacheLongKeyLIRS<V> {
addToStackBottom(e);
}
}
pruneStack();
} else {
removeFromQueue(e);
}
pruneStack();
return old;
}
......@@ -916,14 +913,18 @@ public class CacheLongKeyLIRS<V> {
Entry<V> e = queue.queuePrev;
usedMemory -= e.memory;
removeFromQueue(e);
e.reference = new WeakReference<>(e.value);
e.value = null;
e.memory = 0;
addToQueue(queue2, e);
// the size of the non-resident-cold entries needs to be limited
int maxQueue2Size = nonResidentQueueSize * (mapSize - queue2Size);
if (maxQueue2Size >= 0) {
while (queue2Size > maxQueue2Size) {
e = queue2.queuePrev;
WeakReference<V> reference = e.reference;
if (reference != null && reference.get() != null) {
break; // stop trimming if entry holds a value
}
int hash = getHash(e.key);
remove(e.key, hash);
}
......@@ -1120,10 +1121,15 @@ public class CacheLongKeyLIRS<V> {
*/
V value;
/**
* Weak reference to the value. Set to null for resident entries.
*/
WeakReference<V> reference;
/**
* The estimated memory used.
*/
int memory;
final int memory;
/**
* When the item was last moved to the top of the stack.
......@@ -1156,6 +1162,15 @@ public class CacheLongKeyLIRS<V> {
*/
Entry<V> mapNext;
public Entry() {
this(0);
}
public Entry(int memory) {
this.memory = memory;
}
/**
* Whether this entry is hot. Cold entries are in one of the two queues.
*
......@@ -1165,6 +1180,13 @@ public class CacheLongKeyLIRS<V> {
return queueNext == null;
}
V getValue() {
return value == null ? reference.get() : value;
}
int getMemory() {
return value == null ? 0 : memory;
}
}
/**
......
......@@ -46,13 +46,17 @@ public class TestCacheLongKeyLIRS extends TestBase {
testRandomOperations();
}
private static void testRandomSmallCache() {
private void testRandomSmallCache() {
Random r = new Random(1);
for (int i = 0; i < 10000; i++) {
int j = 0;
StringBuilder buff = new StringBuilder();
CacheLongKeyLIRS<Integer> test = createCache(1 + r.nextInt(10));
int maxSize = 1 + r.nextInt(10);
buff.append("size:").append(maxSize).append('\n');
CacheLongKeyLIRS<Integer> test = createCache(maxSize);
for (; j < 30; j++) {
String lastState = toString(test);
try {
int key = r.nextInt(5);
switch (r.nextInt(3)) {
case 0:
......@@ -69,6 +73,11 @@ public class TestCacheLongKeyLIRS extends TestBase {
buff.append("get ").append(key).append('\n');
test.get(key);
}
verify(test, null);
} catch (Throwable ex) {
println(i + "\n" + buff + "\n" + lastState + "\n" + toString(test));
throw ex;
}
}
}
}
......@@ -164,22 +173,22 @@ public class TestCacheLongKeyLIRS extends TestBase {
assertEquals(1, test.getMemory(5));
assertEquals(0, test.getMemory(4));
assertEquals(0, test.getMemory(100));
assertNull(test.peek(4));
assertNull(test.get(4));
assertNotNull(test.peek(4));
assertNotNull(test.get(4));
assertEquals(10, test.get(1).intValue());
assertEquals(20, test.get(2).intValue());
assertEquals(30, test.get(3).intValue());
verify(test, "mem: 4 stack: 3 2 1 cold: 5 non-resident: 4");
verify(test, "mem: 5 stack: 3 2 1 cold: 4 5 non-resident:");
assertEquals(50, test.get(5).intValue());
verify(test, "mem: 4 stack: 5 3 2 1 cold: 5 non-resident: 4");
verify(test, "mem: 5 stack: 5 3 2 1 cold: 5 4 non-resident:");
assertEquals(50, test.get(5).intValue());
verify(test, "mem: 4 stack: 5 3 2 cold: 1 non-resident: 4");
verify(test, "mem: 5 stack: 5 3 2 cold: 1 4 non-resident:");
// remove
assertEquals(50, test.remove(5).intValue());
assertNull(test.remove(5));
verify(test, "mem: 3 stack: 3 2 1 cold: non-resident: 4");
assertNull(test.remove(4));
verify(test, "mem: 4 stack: 3 2 1 cold: 4 non-resident:");
assertNotNull(test.remove(4));
verify(test, "mem: 3 stack: 3 2 1 cold: non-resident:");
assertNull(test.remove(4));
verify(test, "mem: 3 stack: 3 2 1 cold: non-resident:");
......@@ -195,7 +204,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
verify(test, "mem: 3 stack: 4 3 2 cold: non-resident: 1");
assertEquals(20, test.remove(2).intValue());
assertFalse(test.containsKey(1));
assertNull(test.remove(1));
assertEquals(10, test.remove(1).intValue());
assertFalse(test.containsKey(1));
verify(test, "mem: 2 stack: 4 3 cold: non-resident:");
test.put(1, 10);
......@@ -226,7 +235,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
// 1 was non-resident, so this should make it hot
test.put(1, 10);
verify(test, "mem: 4 stack: 1 5 4 3 cold: 2 non-resident: 5");
assertFalse(test.containsValue(50));
assertTrue(test.containsValue(50));
test.remove(2);
test.remove(3);
test.remove(4);
......@@ -332,8 +341,8 @@ public class TestCacheLongKeyLIRS extends TestBase {
for (int i = 0; i < 20; i++) {
test.put(i, 10 * i);
}
verify(test, "mem: 4 stack: 19 18 17 16 15 14 13 12 11 10 3 2 1 " +
"cold: 19 non-resident: 18 17 16 15 14 13 12 11 10");
verify(test, "mem: 4 stack: 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 " +
"cold: 19 non-resident: 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 0");
}
private void testLimitMemory() {
......@@ -344,13 +353,13 @@ public class TestCacheLongKeyLIRS extends TestBase {
verify(test, "mem: 4 stack: 4 3 2 1 cold: 4 non-resident: 0");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
test.put(6, 60, 3);
verify(test, "mem: 4 stack: 6 4 3 cold: 6 non-resident: 2 1 4");
verify(test, "mem: 4 stack: 6 4 3 cold: 6 non-resident: 2 1 4 0");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
test.put(7, 70, 3);
verify(test, "mem: 4 stack: 7 6 3 cold: 7 non-resident: 6 2 1");
verify(test, "mem: 4 stack: 7 6 4 3 cold: 7 non-resident: 6 2 1 4 0");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
test.put(8, 80, 4);
verify(test, "mem: 4 stack: 8 cold: non-resident:");
verify(test, "mem: 4 stack: 8 cold: non-resident: 3 7 6 2 1 4 0");
assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4);
}
......@@ -369,7 +378,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
test.put(i, i * 10);
test.get(i);
if (log) {
System.out.println("get " + i + " -> " + test);
println("get " + i + " -> " + test);
}
}
verify(test, null);
......@@ -394,14 +403,13 @@ public class TestCacheLongKeyLIRS extends TestBase {
}
verify(test, null);
}
// ensure 0..9 are hot, 10..17 are not resident, 18..19 are cold
for (int i = 0; i < size; i++) {
Integer x = test.get(i);
if (i < size / 2 || i == size - 1 || i == size - 2) {
assertNotNull("i: " + i, x);
assertEquals(i * 10, x.intValue());
} else {
assertNull(x);
}
verify(test, null);
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论