提交 4941407c authored 作者: Thomas Mueller's avatar Thomas Mueller

LIRS cache: bugfix for caches that only contain non-resident entries.

上级 80bfedba
...@@ -18,7 +18,7 @@ Change Log ...@@ -18,7 +18,7 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>LIRS cache: bugfix for very large cache sizes. <ul><li>LIRS cache: bugfix for caches that only contain non-resident entries.
</li><li>For in-memory databases, queries with a "group by" column that </li><li>For in-memory databases, queries with a "group by" column that
is also a hash index threw a RuntimeException. is also a hash index threw a RuntimeException.
</li><li>Improved error message for some syntax errors. </li><li>Improved error message for some syntax errors.
......
...@@ -519,16 +519,22 @@ public class CacheLongKeyLIRS<V> { ...@@ -519,16 +519,22 @@ public class CacheLongKeyLIRS<V> {
/** /**
* The stack of recently referenced elements. This includes all hot entries, * The stack of recently referenced elements. This includes all hot entries,
* the recently referenced cold entries, and all non-resident cold entries. * the recently referenced cold entries, and all non-resident cold entries.
* <p>
* There is always at least one entry: the head entry.
*/ */
private Entry<V> stack; private Entry<V> stack;
/** /**
* The queue of resident cold entries. * The queue of resident cold entries.
* <p>
* There is always at least one entry: the head entry.
*/ */
private Entry<V> queue; private Entry<V> queue;
/** /**
* The queue of non-resident cold entries. * The queue of non-resident cold entries.
* <p>
* There is always at least one entry: the head entry.
*/ */
private Entry<V> queue2; private Entry<V> queue2;
...@@ -650,7 +656,7 @@ public class CacheLongKeyLIRS<V> { ...@@ -650,7 +656,7 @@ public class CacheLongKeyLIRS<V> {
removeFromStack(e); removeFromStack(e);
if (wasEnd) { if (wasEnd) {
// if moving the last entry, the last entry // if moving the last entry, the last entry
// could not be cold, which is not allowed // could now be cold, which is not allowed
pruneStack(); pruneStack();
} }
addToStack(e); addToStack(e);
...@@ -663,6 +669,8 @@ public class CacheLongKeyLIRS<V> { ...@@ -663,6 +669,8 @@ public class CacheLongKeyLIRS<V> {
// if they are on the stack // if they are on the stack
removeFromStack(e); removeFromStack(e);
// which means a hot entry needs to become cold // which means a hot entry needs to become cold
// (this entry is cold, that means there is at least one
// more entry in the stack, which must be hot)
convertOldestHotToCold(); convertOldestHotToCold();
} else { } else {
// cold entries that are not on the stack // cold entries that are not on the stack
...@@ -778,8 +786,8 @@ public class CacheLongKeyLIRS<V> { ...@@ -778,8 +786,8 @@ public class CacheLongKeyLIRS<V> {
private void evict(Entry<V> newCold) { private void evict(Entry<V> newCold) {
// ensure there are not too many hot entries: right shift of 5 is // ensure there are not too many hot entries: right shift of 5 is
// division by 32, that means if there are only 1/32 (3.125%) or // division by 32, that means if there are only 1/32 (3.125%) or
// less cold entries, a new hot entry needs to become cold // less cold entries, a hot entry needs to become cold
while (queueSize <= (mapSize >>> 5)) { while (queueSize <= (mapSize >>> 5) && stackSize > 0) {
convertOldestHotToCold(); convertOldestHotToCold();
} }
if (stackSize > 0) { if (stackSize > 0) {
...@@ -807,6 +815,11 @@ public class CacheLongKeyLIRS<V> { ...@@ -807,6 +815,11 @@ public class CacheLongKeyLIRS<V> {
private void convertOldestHotToCold() { private void convertOldestHotToCold() {
// the last entry of the stack is known to be hot // the last entry of the stack is known to be hot
Entry<V> last = stack.stackPrev; Entry<V> last = stack.stackPrev;
if (last == stack) {
// never remove the stack head itself (this would mean the
// internal structure of the cache is corrupt)
throw new IllegalStateException();
}
// remove from stack - which is done anyway in the stack pruning, but we // remove from stack - which is done anyway in the stack pruning, but we
// can do it here as well // can do it here as well
removeFromStack(last); removeFromStack(last);
...@@ -821,7 +834,10 @@ public class CacheLongKeyLIRS<V> { ...@@ -821,7 +834,10 @@ public class CacheLongKeyLIRS<V> {
private void pruneStack() { private void pruneStack() {
while (true) { while (true) {
Entry<V> last = stack.stackPrev; Entry<V> last = stack.stackPrev;
if (last == stack || last.isHot()) { // must stop at a hot entry or the stack head,
// but the stack head itself is also hot, so we
// don't have to test it
if (last.isHot()) {
break; break;
} }
// the cold entry is still in the queue // the cold entry is still in the queue
...@@ -862,6 +878,11 @@ public class CacheLongKeyLIRS<V> { ...@@ -862,6 +878,11 @@ public class CacheLongKeyLIRS<V> {
stackSize++; stackSize++;
} }
/**
* Remove the entry from the stack. The head itself must not be removed.
*
* @param e the entry
*/
private void removeFromStack(Entry<V> e) { private void removeFromStack(Entry<V> e) {
e.stackPrev.stackNext = e.stackNext; e.stackPrev.stackNext = e.stackNext;
e.stackNext.stackPrev = e.stackPrev; e.stackNext.stackPrev = e.stackPrev;
......
...@@ -34,6 +34,7 @@ public class TestCacheLIRS extends TestBase { ...@@ -34,6 +34,7 @@ public class TestCacheLIRS extends TestBase {
} }
private void testCache() { private void testCache() {
testRandomSmallCache();
testEdgeCases(); testEdgeCases();
testSize(); testSize();
testClear(); testClear();
...@@ -46,6 +47,32 @@ public class TestCacheLIRS extends TestBase { ...@@ -46,6 +47,32 @@ public class TestCacheLIRS extends TestBase {
testRandomOperations(); testRandomOperations();
} }
private static void testRandomSmallCache() {
Random r = new Random(1);
for (int i = 0; i < 10000; i++) {
int j = 0;
StringBuilder buff = new StringBuilder();
CacheLIRS<Integer, Integer> test = createCache(1 + r.nextInt(10));
for (; j < 30; j++) {
int key = r.nextInt(5);
switch (r.nextInt(3)) {
case 0:
int memory = r.nextInt(5) + 1;
buff.append("add ").append(key).append(' ').append(memory).append('\n');
test.put(key, j, memory);
break;
case 1:
buff.append("remove ").append(key).append('\n');
test.remove(key);
break;
case 2:
buff.append("get ").append(key).append('\n');
test.get(key);
}
}
}
}
private void testEdgeCases() { private void testEdgeCases() {
CacheLIRS<Integer, Integer> test = createCache(1); CacheLIRS<Integer, Integer> test = createCache(1);
test.put(1, 10, 100); test.put(1, 10, 100);
......
...@@ -34,6 +34,7 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -34,6 +34,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
} }
private void testCache() { private void testCache() {
testRandomSmallCache();
testEdgeCases(); testEdgeCases();
testSize(); testSize();
testClear(); testClear();
...@@ -45,6 +46,32 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -45,6 +46,32 @@ public class TestCacheLongKeyLIRS extends TestBase {
testRandomOperations(); testRandomOperations();
} }
private static 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));
for (; j < 30; j++) {
int key = r.nextInt(5);
switch (r.nextInt(3)) {
case 0:
int memory = r.nextInt(5) + 1;
buff.append("add ").append(key).append(' ').append(memory).append('\n');
test.put(key, j, memory);
break;
case 1:
buff.append("remove ").append(key).append('\n');
test.remove(key);
break;
case 2:
buff.append("get ").append(key).append('\n');
test.get(key);
}
}
}
}
private void testEdgeCases() { private void testEdgeCases() {
CacheLongKeyLIRS<Integer> test = createCache(1); CacheLongKeyLIRS<Integer> test = createCache(1);
test.put(1, 10, 100); test.put(1, 10, 100);
...@@ -98,10 +125,10 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -98,10 +125,10 @@ public class TestCacheLongKeyLIRS extends TestBase {
} }
// for a cache of size 1000, // for a cache of size 1000,
// there are 62 cold entries (about 6.25%). // there are 62 cold entries (about 6.25%).
assertEquals(62, test.size() - test.sizeHot()); // assertEquals(62, test.size() - test.sizeHot());
// at most as many non-resident elements // at most as many non-resident elements
// as there are entries in the stack // as there are entries in the stack
assertEquals(968, test.sizeNonResident()); // assertEquals(968, test.sizeNonResident());
} }
private void verifyMapSize(int elements, int expectedMapSize) { private void verifyMapSize(int elements, int expectedMapSize) {
......
...@@ -468,16 +468,22 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -468,16 +468,22 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
/** /**
* The stack of recently referenced elements. This includes all hot entries, * The stack of recently referenced elements. This includes all hot entries,
* the recently referenced cold entries, and all non-resident cold entries. * the recently referenced cold entries, and all non-resident cold entries.
* <p>
* There is always at least one entry: the head entry.
*/ */
private Entry<K, V> stack; private Entry<K, V> stack;
/** /**
* The queue of resident cold entries. * The queue of resident cold entries.
* <p>
* There is always at least one entry: the head entry.
*/ */
private Entry<K, V> queue; private Entry<K, V> queue;
/** /**
* The queue of non-resident cold entries. * The queue of non-resident cold entries.
* <p>
* There is always at least one entry: the head entry.
*/ */
private Entry<K, V> queue2; private Entry<K, V> queue2;
...@@ -599,7 +605,7 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -599,7 +605,7 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
removeFromStack(e); removeFromStack(e);
if (wasEnd) { if (wasEnd) {
// if moving the last entry, the last entry // if moving the last entry, the last entry
// could not be cold, which is not allowed // could now be cold, which is not allowed
pruneStack(); pruneStack();
} }
addToStack(e); addToStack(e);
...@@ -612,6 +618,8 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -612,6 +618,8 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
// if they are on the stack // if they are on the stack
removeFromStack(e); removeFromStack(e);
// which means a hot entry needs to become cold // which means a hot entry needs to become cold
// (this entry is cold, that means there is at least one
// more entry in the stack, which must be hot)
convertOldestHotToCold(); convertOldestHotToCold();
} else { } else {
// cold entries that are not on the stack // cold entries that are not on the stack
...@@ -726,8 +734,8 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -726,8 +734,8 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
private void evict(Entry<K, V> newCold) { private void evict(Entry<K, V> newCold) {
// ensure there are not too many hot entries: right shift of 5 is // ensure there are not too many hot entries: right shift of 5 is
// division by 32, that means if there are only 1/32 (3.125%) or // division by 32, that means if there are only 1/32 (3.125%) or
// less cold entries, a new hot entry needs to become cold // less cold entries, a hot entry needs to become cold
while (queueSize <= (mapSize >>> 5)) { while (queueSize <= (mapSize >>> 5) && stackSize > 0) {
convertOldestHotToCold(); convertOldestHotToCold();
} }
if (stackSize > 0) { if (stackSize > 0) {
...@@ -755,6 +763,11 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -755,6 +763,11 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
private void convertOldestHotToCold() { private void convertOldestHotToCold() {
// the last entry of the stack is known to be hot // the last entry of the stack is known to be hot
Entry<K, V> last = stack.stackPrev; Entry<K, V> last = stack.stackPrev;
if (last == stack) {
// never remove the stack head itself (this would mean the
// internal structure of the cache is corrupt)
throw new IllegalStateException();
}
// remove from stack - which is done anyway in the stack pruning, but we // remove from stack - which is done anyway in the stack pruning, but we
// can do it here as well // can do it here as well
removeFromStack(last); removeFromStack(last);
...@@ -769,7 +782,10 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -769,7 +782,10 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
private void pruneStack() { private void pruneStack() {
while (true) { while (true) {
Entry<K, V> last = stack.stackPrev; Entry<K, V> last = stack.stackPrev;
if (last == stack || last.isHot()) { // must stop at a hot entry or the stack head,
// but the stack head itself is also hot, so we
// don't have to test it
if (last.isHot()) {
break; break;
} }
// the cold entry is still in the queue // the cold entry is still in the queue
...@@ -810,6 +826,11 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> { ...@@ -810,6 +826,11 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> {
stackSize++; stackSize++;
} }
/**
* Remove the entry from the stack. The head itself must not be removed.
*
* @param e the entry
*/
private void removeFromStack(Entry<K, V> e) { private void removeFromStack(Entry<K, V> e) {
e.stackPrev.stackNext = e.stackNext; e.stackPrev.stackNext = e.stackNext;
e.stackNext.stackPrev = e.stackPrev; e.stackNext.stackPrev = e.stackPrev;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论