提交 38e8ce1c authored 作者: Thomas Mueller's avatar Thomas Mueller

LIRS cache: concurrent and concurrent with long key

上级 9199e123
...@@ -31,7 +31,7 @@ public class TestCacheConcurrentLIRS extends TestBase { ...@@ -31,7 +31,7 @@ public class TestCacheConcurrentLIRS extends TestBase {
} }
private static void testConcurrent() { private static void testConcurrent() {
final CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(100, 1); final CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(100);
int threadCount = 8; int threadCount = 8;
final CountDownLatch wait = new CountDownLatch(1); final CountDownLatch wait = new CountDownLatch(1);
final AtomicBoolean stopped = new AtomicBoolean(); final AtomicBoolean stopped = new AtomicBoolean();
......
...@@ -41,7 +41,7 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -41,7 +41,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
} }
private void testEdgeCases() { private void testEdgeCases() {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(1, 1); CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(1);
test.put(1, 10, 100); test.put(1, 10, 100);
assertEquals(10, test.get(1).intValue()); assertEquals(10, test.get(1).intValue());
try { try {
...@@ -90,16 +90,16 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -90,16 +90,16 @@ public class TestCacheLongKeyLIRS extends TestBase {
private void verifyMapSize(int elements, int mapSize) { private void verifyMapSize(int elements, int mapSize) {
CacheLongKeyLIRS<Integer> test; CacheLongKeyLIRS<Integer> test;
test = CacheLongKeyLIRS.newInstance(elements - 1, 1); test = CacheLongKeyLIRS.newInstance(elements - 1);
assertTrue(mapSize > test.sizeMapArray()); assertTrue(mapSize > test.sizeMapArray());
test = CacheLongKeyLIRS.newInstance(elements, 1); test = CacheLongKeyLIRS.newInstance(elements);
assertEquals(mapSize, test.sizeMapArray()); assertEquals(mapSize, test.sizeMapArray());
test = CacheLongKeyLIRS.newInstance(elements * 100, 100); test = CacheLongKeyLIRS.newInstance(elements * 100, 100, 16, 10);
assertEquals(mapSize, test.sizeMapArray()); assertEquals(mapSize, test.sizeMapArray());
} }
private void testGetPutPeekRemove() { private void testGetPutPeekRemove() {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(4, 1); CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(4);
test.put(1, 10); test.put(1, 10);
test.put(2, 20); test.put(2, 20);
test.put(3, 30); test.put(3, 30);
...@@ -216,7 +216,7 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -216,7 +216,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
} }
private void testPruneStack() { private void testPruneStack() {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(5, 1); CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(5);
for (int i = 0; i < 7; i++) { for (int i = 0; i < 7; i++) {
test.put(i, i * 10); test.put(i, i * 10);
} }
...@@ -235,7 +235,7 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -235,7 +235,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
} }
private void testClear() { private void testClear() {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(40, 10); CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(40, 10, 16, 1);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
test.put(i, 10 * i, 9); test.put(i, 10 * i, 9);
} }
...@@ -284,7 +284,7 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -284,7 +284,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
} }
private void testLimitHot() { private void testLimitHot() {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(100, 1); CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(100);
for (int i = 0; i < 300; i++) { for (int i = 0; i < 300; i++) {
test.put(i, 10 * i); test.put(i, 10 * i);
} }
...@@ -294,7 +294,7 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -294,7 +294,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
} }
private void testLimitNonResident() { private void testLimitNonResident() {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(4, 1); CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(4);
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
test.put(i, 10 * i); test.put(i, 10 * i);
} }
...@@ -305,7 +305,7 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -305,7 +305,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
boolean log = false; boolean log = false;
int size = 20; int size = 20;
// cache size 11 (10 hot, 1 cold) // cache size 11 (10 hot, 1 cold)
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(size / 2 + 1, 1); CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(size / 2 + 1);
// init the cache with some dummy entries // init the cache with some dummy entries
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
test.put(-i, -i * 10); test.put(-i, -i * 10);
...@@ -359,7 +359,7 @@ public class TestCacheLongKeyLIRS extends TestBase { ...@@ -359,7 +359,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
int size = 10; int size = 10;
Random r = new Random(1); Random r = new Random(1);
for (int j = 0; j < 100; j++) { for (int j = 0; j < 100; j++) {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(size / 2, 1); CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(size / 2);
HashMap<Integer, Integer> good = New.hashMap(); HashMap<Integer, Integer> good = New.hashMap();
for (int i = 0; i < 10000; i++) { for (int i = 0; i < 10000; i++) {
int key = r.nextInt(size); int key = r.nextInt(size);
......
/* /*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, * Copyright 2012 H2 Group (http://h2database.com).
* Version 1.0, and under the Eclipse Public License, Version 1.0 * All Rights Reserved.
* (http://h2database.com/html/license.html). *
* Initial Developer: H2 Group * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ */
package org.h2.dev.store.btree; package org.h2.dev.store.btree;
...@@ -37,8 +47,8 @@ import java.util.concurrent.ConcurrentMap; ...@@ -37,8 +47,8 @@ import java.util.concurrent.ConcurrentMap;
* an individual LIRS cache. * an individual LIRS cache.
* <p> * <p>
* Accessed entries are only moved to the top of the stack if at least a number * Accessed entries are only moved to the top of the stack if at least a number
* of other entries have been moved to the front. Write access and moving * of other entries have been moved to the front (1% by default). Write access
* entries to the top of the stack is synchronized per segment. * and moving entries to the top of the stack is synchronized per segment.
* *
* @author Thomas Mueller * @author Thomas Mueller
* @param <K> the key type * @param <K> the key type
...@@ -126,7 +136,7 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc ...@@ -126,7 +136,7 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc
*/ */
public V put(K key, V value, int memory) { public V put(K key, V value, int memory) {
int hash = getHash(key); int hash = getHash(key);
return getSegment(hash).put(key, value, hash, memory); return getSegment(hash).put(key, hash, value, memory);
} }
/** /**
...@@ -141,50 +151,23 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc ...@@ -141,50 +151,23 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc
} }
public V putIfAbsent(K key, V value) { public V putIfAbsent(K key, V value) {
int todo; int hash = getHash(key);
if (containsKey(key)) { return getSegment(hash).putIfAbsent(key, hash, value);
return get(key);
}
return put(key, value);
} }
public boolean remove(Object key, Object value) { public boolean remove(Object key, Object value) {
int todo; int hash = getHash(key);
Entry<K, V> e = find(key); return getSegment(hash).remove(key, hash, value);
if (e != null) {
V x = e.value;
if (x != null && x.equals(value)) {
remove(key);
return true;
}
}
return false;
} }
public boolean replace(K key, V oldValue, V newValue) { public boolean replace(K key, V oldValue, V newValue) {
int todo; int hash = getHash(key);
Entry<K, V> e = find(key); return getSegment(hash).replace(key, hash, oldValue, newValue);
if (e != null) {
V x = e.value;
if (x != null && x.equals(oldValue)) {
put(key, newValue);
return true;
}
}
return false;
} }
public V replace(K key, V value) { public V replace(K key, V value) {
int todo; int hash = getHash(key);
Entry<K, V> e = find(key); return getSegment(hash).replace(key, hash, value);
if (e != null) {
V x = e.value;
if (x != null) {
put(key, value);
return x;
}
}
return null;
} }
/** /**
...@@ -305,9 +288,19 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc ...@@ -305,9 +288,19 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc
} }
/** /**
* Create a new cache with the given memory size. To just limit the number * Create a new cache with the given number of entries, and the default
* of entries, use the required number as the maximum memory, and an average * settings (an average size of 1 per entry, 16 segments, and stack move
* size of 1. * distance equals to the max entry size divided by 100).
*
* @param maxEntries the maximum number of entries
* @return the cache
*/
public static <K, V> CacheConcurrentLIRS<K, V> newInstance(int maxEntries) {
return new CacheConcurrentLIRS<K, V>(maxEntries, 1, 16, maxEntries / 100);
}
/**
* Create a new cache with the given memory size.
* *
* @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)
...@@ -622,11 +615,7 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc ...@@ -622,11 +615,7 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc
} }
} }
V put(K key, V value, int hash) { synchronized V put(K key, int hash, V value, int memory) {
return put(key, value, hash, averageMemory);
}
synchronized V put(K key, V value, int hash, int memory) {
if (value == null) { if (value == null) {
throw new NullPointerException(); throw new NullPointerException();
} }
...@@ -656,6 +645,50 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc ...@@ -656,6 +645,50 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc
return old; return old;
} }
synchronized V putIfAbsent(K key, int hash, V value) {
Entry<K, V> e = find(key, hash);
if (e != null && e.value != null) {
return e.value;
}
return put(key, hash, value, averageMemory);
}
synchronized boolean remove(Object key, int hash, Object value) {
Entry<K, V> e = find(key, hash);
if (e != null) {
V x = e.value;
if (x != null && x.equals(value)) {
remove(key, hash);
return true;
}
}
return false;
}
synchronized boolean replace(K key, int hash, V oldValue, V newValue) {
Entry<K, V> e = find(key, hash);
if (e != null) {
V x = e.value;
if (x != null && x.equals(oldValue)) {
put(key, hash, newValue, averageMemory);
return true;
}
}
return false;
}
synchronized V replace(K key, int hash, V value) {
Entry<K, V> e = find(key, hash);
if (e != null) {
V x = e.value;
if (x != null) {
put(key, hash, value, averageMemory);
return x;
}
}
return null;
}
synchronized V remove(Object key, int hash) { synchronized V remove(Object key, int hash) {
int index = hash & mask; int index = hash & mask;
Entry<K, V> e = entries[index]; Entry<K, V> e = entries[index];
......
...@@ -53,25 +53,29 @@ public class CacheLongKeyLIRS<V> { ...@@ -53,25 +53,29 @@ public class CacheLongKeyLIRS<V> {
private Segment<V>[] segments; private Segment<V>[] segments;
private int segmentCount;
private int segmentShift; private int segmentShift;
private int segmentMask; private int segmentMask;
private final int stackMoveDistance;
private CacheLongKeyLIRS(long maxMemory, int averageMemory) { private CacheLongKeyLIRS(long maxMemory, int averageMemory, int segmentCount, int stackMoveDistance) {
this.maxMemory = maxMemory; setMaxMemory(maxMemory);
this.averageMemory = averageMemory; setAverageMemory(averageMemory);
if (Integer.bitCount(segmentCount) != 1) {
throw new IllegalArgumentException("The segment count must be a power of 2, is " + segmentCount);
}
this.segmentCount = segmentCount;
this.stackMoveDistance = stackMoveDistance;
clear(); clear();
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void clear() { public void clear() {
// must be a power of 2 segmentMask = segmentCount - 1;
int count = 16; segments = new Segment[segmentCount];
segmentMask = count - 1; for (int i = 0; i < segmentCount; i++) {
segments = new Segment[count];
long max = 1 + maxMemory / segments.length;
for (int i = 0; i < count; i++) {
segments[i] = new Segment<V>( segments[i] = new Segment<V>(
max, averageMemory); 1 + maxMemory / segmentCount, averageMemory, stackMoveDistance);
} }
segmentShift = Integer.numberOfTrailingZeros(segments[0].sizeMapArray()); segmentShift = Integer.numberOfTrailingZeros(segments[0].sizeMapArray());
} }
...@@ -117,7 +121,7 @@ public class CacheLongKeyLIRS<V> { ...@@ -117,7 +121,7 @@ public class CacheLongKeyLIRS<V> {
*/ */
public V put(long key, V value, int memory) { public V put(long key, V value, int memory) {
int hash = getHash(key); int hash = getHash(key);
return getSegment(hash).put(key, value, hash, memory); return getSegment(hash).put(key, hash, value, memory);
} }
/** /**
...@@ -205,11 +209,13 @@ public class CacheLongKeyLIRS<V> { ...@@ -205,11 +209,13 @@ public class CacheLongKeyLIRS<V> {
throw new IllegalArgumentException("Max memory must be larger than 0"); throw new IllegalArgumentException("Max memory must be larger than 0");
} }
this.maxMemory = maxMemory; this.maxMemory = maxMemory;
if (segments != null) {
long max = 1 + maxMemory / segments.length; long max = 1 + maxMemory / segments.length;
for (Segment<V> s : segments) { for (Segment<V> s : segments) {
s.setMaxMemory(max); s.setMaxMemory(max);
} }
} }
}
/** /**
* Set the average memory used per entry. It is used to calculate the length * Set the average memory used per entry. It is used to calculate the length
...@@ -222,10 +228,12 @@ public class CacheLongKeyLIRS<V> { ...@@ -222,10 +228,12 @@ public class CacheLongKeyLIRS<V> {
throw new IllegalArgumentException("Average memory must be larger than 0"); throw new IllegalArgumentException("Average memory must be larger than 0");
} }
this.averageMemory = averageMemory; this.averageMemory = averageMemory;
if (segments != null) {
for (Segment<V> s : segments) { for (Segment<V> s : segments) {
s.setAverageMemory(averageMemory); s.setAverageMemory(averageMemory);
} }
} }
}
/** /**
* Get the average memory used per entry. * Get the average memory used per entry.
...@@ -245,6 +253,18 @@ public class CacheLongKeyLIRS<V> { ...@@ -245,6 +253,18 @@ public class CacheLongKeyLIRS<V> {
return maxMemory; return maxMemory;
} }
/**
* Create a new cache with the given number of entries, and the default
* settings (an average size of 1 per entry, 16 segments, and stack move
* distance equals to the max entry size divided by 100).
*
* @param maxEntries the maximum number of entries
* @return the cache
*/
public static <K, V> CacheLongKeyLIRS<V> newInstance(int maxEntries) {
return new CacheLongKeyLIRS<V>(maxEntries, 1, 16, maxEntries / 100);
}
/** /**
* Create a new cache with the given memory size. To just limit the number * 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 * of entries, use the required number as the maximum memory, and an average
...@@ -254,8 +274,9 @@ public class CacheLongKeyLIRS<V> { ...@@ -254,8 +274,9 @@ public class CacheLongKeyLIRS<V> {
* @param averageMemory the average memory (1 or larger) * @param averageMemory the average memory (1 or larger)
* @return the cache * @return the cache
*/ */
public static <V> CacheLongKeyLIRS<V> newInstance(int maxMemory, int averageMemory) { public static <V> CacheLongKeyLIRS<V> newInstance(int maxMemory, int averageMemory,
return new CacheLongKeyLIRS<V>(maxMemory, averageMemory); int segmentCount, int stackMoveDistance) {
return new CacheLongKeyLIRS<V>(maxMemory, averageMemory, segmentCount, stackMoveDistance);
} }
/** /**
...@@ -397,12 +418,11 @@ public class CacheLongKeyLIRS<V> { ...@@ -397,12 +418,11 @@ public class CacheLongKeyLIRS<V> {
*/ */
static class Segment<V> { static class Segment<V> {
/** /**
* How many other item are to be moved to the top of the stack before * How many other item are to be moved to the top of the stack before
* the current item is moved. * the current item is moved.
*/ */
private int stackMoveDistance = 20; private final int stackMoveDistance;
/** /**
* The maximum memory this cache should use. * The maximum memory this cache should use.
...@@ -476,10 +496,13 @@ public class CacheLongKeyLIRS<V> { ...@@ -476,10 +496,13 @@ public class CacheLongKeyLIRS<V> {
* *
* @param maxMemory the maximum memory to use * @param maxMemory the maximum memory to use
* @param averageMemory the average memory usage of an object * @param averageMemory the average memory usage of an object
* @param stackMoveDistance the number of other entries to be moved to
* the top of the stack before moving an entry to the top
*/ */
Segment(long maxMemory, int averageMemory) { Segment(long maxMemory, int averageMemory, int stackMoveDistance) {
setMaxMemory(maxMemory); setMaxMemory(maxMemory);
setAverageMemory(averageMemory); setAverageMemory(averageMemory);
this.stackMoveDistance = stackMoveDistance;
clear(); clear();
} }
...@@ -594,11 +617,7 @@ public class CacheLongKeyLIRS<V> { ...@@ -594,11 +617,7 @@ public class CacheLongKeyLIRS<V> {
} }
} }
V put(long key, V value, int hash) { synchronized V put(long key, int hash, V value, int memory) {
return put(key, value, hash, averageMemory);
}
synchronized V put(long key, V value, int hash, int memory) {
if (value == null) { if (value == null) {
throw new NullPointerException(); throw new NullPointerException();
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论