提交 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 {
}
private static void testConcurrent() {
final CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(100, 1);
final CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(100);
int threadCount = 8;
final CountDownLatch wait = new CountDownLatch(1);
final AtomicBoolean stopped = new AtomicBoolean();
......
......@@ -41,7 +41,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
}
private void testEdgeCases() {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(1, 1);
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(1);
test.put(1, 10, 100);
assertEquals(10, test.get(1).intValue());
try {
......@@ -90,16 +90,16 @@ public class TestCacheLongKeyLIRS extends TestBase {
private void verifyMapSize(int elements, int mapSize) {
CacheLongKeyLIRS<Integer> test;
test = CacheLongKeyLIRS.newInstance(elements - 1, 1);
test = CacheLongKeyLIRS.newInstance(elements - 1);
assertTrue(mapSize > test.sizeMapArray());
test = CacheLongKeyLIRS.newInstance(elements, 1);
test = CacheLongKeyLIRS.newInstance(elements);
assertEquals(mapSize, test.sizeMapArray());
test = CacheLongKeyLIRS.newInstance(elements * 100, 100);
test = CacheLongKeyLIRS.newInstance(elements * 100, 100, 16, 10);
assertEquals(mapSize, test.sizeMapArray());
}
private void testGetPutPeekRemove() {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(4, 1);
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(4);
test.put(1, 10);
test.put(2, 20);
test.put(3, 30);
......@@ -216,7 +216,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
}
private void testPruneStack() {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(5, 1);
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(5);
for (int i = 0; i < 7; i++) {
test.put(i, i * 10);
}
......@@ -235,7 +235,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
}
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++) {
test.put(i, 10 * i, 9);
}
......@@ -284,7 +284,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
}
private void testLimitHot() {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(100, 1);
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(100);
for (int i = 0; i < 300; i++) {
test.put(i, 10 * i);
}
......@@ -294,7 +294,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
}
private void testLimitNonResident() {
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(4, 1);
CacheLongKeyLIRS<Integer> test = CacheLongKeyLIRS.newInstance(4);
for (int i = 0; i < 20; i++) {
test.put(i, 10 * i);
}
......@@ -305,7 +305,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
boolean log = false;
int size = 20;
// 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
for (int i = 0; i < size; i++) {
test.put(-i, -i * 10);
......@@ -359,7 +359,7 @@ public class TestCacheLongKeyLIRS extends TestBase {
int size = 10;
Random r = new Random(1);
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();
for (int i = 0; i < 10000; i++) {
int key = r.nextInt(size);
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
* Copyright 2012 H2 Group (http://h2database.com).
* All Rights Reserved.
*
* 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;
......@@ -37,8 +47,8 @@ import java.util.concurrent.ConcurrentMap;
* an individual LIRS cache.
* <p>
* 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
* entries to the top of the stack is synchronized per segment.
* of other entries have been moved to the front (1% by default). Write access
* and moving entries to the top of the stack is synchronized per segment.
*
* @author Thomas Mueller
* @param <K> the key type
......@@ -126,7 +136,7 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc
*/
public V put(K key, V value, int memory) {
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
}
public V putIfAbsent(K key, V value) {
int todo;
if (containsKey(key)) {
return get(key);
}
return put(key, value);
int hash = getHash(key);
return getSegment(hash).putIfAbsent(key, hash, value);
}
public boolean remove(Object key, Object value) {
int todo;
Entry<K, V> e = find(key);
if (e != null) {
V x = e.value;
if (x != null && x.equals(value)) {
remove(key);
return true;
}
}
return false;
int hash = getHash(key);
return getSegment(hash).remove(key, hash, value);
}
public boolean replace(K key, V oldValue, V newValue) {
int todo;
Entry<K, V> e = find(key);
if (e != null) {
V x = e.value;
if (x != null && x.equals(oldValue)) {
put(key, newValue);
return true;
}
}
return false;
int hash = getHash(key);
return getSegment(hash).replace(key, hash, oldValue, newValue);
}
public V replace(K key, V value) {
int todo;
Entry<K, V> e = find(key);
if (e != null) {
V x = e.value;
if (x != null) {
put(key, value);
return x;
}
}
return null;
int hash = getHash(key);
return getSegment(hash).replace(key, hash, value);
}
/**
......@@ -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
* of entries, use the required number as the maximum memory, and an average
* size of 1.
* 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> 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 averageMemory the average memory (1 or larger)
......@@ -622,11 +615,7 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc
}
}
V put(K key, V value, int hash) {
return put(key, value, hash, averageMemory);
}
synchronized V put(K key, V value, int hash, int memory) {
synchronized V put(K key, int hash, V value, int memory) {
if (value == null) {
throw new NullPointerException();
}
......@@ -656,6 +645,50 @@ public class CacheConcurrentLIRS<K, V> extends AbstractMap<K, V> implements Conc
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) {
int index = hash & mask;
Entry<K, V> e = entries[index];
......
......@@ -53,25 +53,29 @@ public class CacheLongKeyLIRS<V> {
private Segment<V>[] segments;
private int segmentCount;
private int segmentShift;
private int segmentMask;
private final int stackMoveDistance;
private CacheLongKeyLIRS(long maxMemory, int averageMemory) {
this.maxMemory = maxMemory;
this.averageMemory = averageMemory;
private CacheLongKeyLIRS(long maxMemory, int averageMemory, int segmentCount, int stackMoveDistance) {
setMaxMemory(maxMemory);
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();
}
@SuppressWarnings("unchecked")
public void clear() {
// must be a power of 2
int count = 16;
segmentMask = count - 1;
segments = new Segment[count];
long max = 1 + maxMemory / segments.length;
for (int i = 0; i < count; i++) {
segmentMask = segmentCount - 1;
segments = new Segment[segmentCount];
for (int i = 0; i < segmentCount; i++) {
segments[i] = new Segment<V>(
max, averageMemory);
1 + maxMemory / segmentCount, averageMemory, stackMoveDistance);
}
segmentShift = Integer.numberOfTrailingZeros(segments[0].sizeMapArray());
}
......@@ -117,7 +121,7 @@ public class CacheLongKeyLIRS<V> {
*/
public V put(long key, V value, int memory) {
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> {
throw new IllegalArgumentException("Max memory must be larger than 0");
}
this.maxMemory = maxMemory;
if (segments != null) {
long max = 1 + maxMemory / segments.length;
for (Segment<V> s : segments) {
s.setMaxMemory(max);
}
}
}
/**
* Set the average memory used per entry. It is used to calculate the length
......@@ -222,10 +228,12 @@ public class CacheLongKeyLIRS<V> {
throw new IllegalArgumentException("Average memory must be larger than 0");
}
this.averageMemory = averageMemory;
if (segments != null) {
for (Segment<V> s : segments) {
s.setAverageMemory(averageMemory);
}
}
}
/**
* Get the average memory used per entry.
......@@ -245,6 +253,18 @@ public class CacheLongKeyLIRS<V> {
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
* of entries, use the required number as the maximum memory, and an average
......@@ -254,8 +274,9 @@ public class CacheLongKeyLIRS<V> {
* @param averageMemory the average memory (1 or larger)
* @return the cache
*/
public static <V> CacheLongKeyLIRS<V> newInstance(int maxMemory, int averageMemory) {
return new CacheLongKeyLIRS<V>(maxMemory, averageMemory);
public static <V> CacheLongKeyLIRS<V> newInstance(int maxMemory, int averageMemory,
int segmentCount, int stackMoveDistance) {
return new CacheLongKeyLIRS<V>(maxMemory, averageMemory, segmentCount, stackMoveDistance);
}
/**
......@@ -397,12 +418,11 @@ public class CacheLongKeyLIRS<V> {
*/
static class Segment<V> {
/**
* How many other item are to be moved to the top of the stack before
* the current item is moved.
*/
private int stackMoveDistance = 20;
private final int stackMoveDistance;
/**
* The maximum memory this cache should use.
......@@ -476,10 +496,13 @@ public class CacheLongKeyLIRS<V> {
*
* @param maxMemory the maximum memory to use
* @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);
setAverageMemory(averageMemory);
this.stackMoveDistance = stackMoveDistance;
clear();
}
......@@ -594,11 +617,7 @@ public class CacheLongKeyLIRS<V> {
}
}
V put(long key, V value, int hash) {
return put(key, value, hash, averageMemory);
}
synchronized V put(long key, V value, int hash, int memory) {
synchronized V put(long key, int hash, V value, int memory) {
if (value == null) {
throw new NullPointerException();
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论