提交 002ebdc9 authored 作者: noelgrandin's avatar noelgrandin

MVStore - improvement to free space management - saves us from having to…

MVStore - improvement to free space management - saves us from having to generate a large BitSet every time we need to allocate a chunk
上级 a45f14e4
package org.h2.mvstore;
import java.util.ArrayList;
import java.util.List;
public class FreeSpaceList {
/** the first 2 pages are occupied by the file header. */
private static final int FIRST_FREE_PAGE = 2;
/** I use max_value/2 to avoid overflow errors during arithmetic. */
private static final int MAX_NO_PAGES = Integer.MAX_VALUE / 2;
private List<PageRange> freeSpaceList = new ArrayList<PageRange>();
public FreeSpaceList() {
freeSpaceList.add(new PageRange(FIRST_FREE_PAGE, MAX_NO_PAGES));
}
private static final class PageRange {
/** the start point, in pages */
public int start;
/** the length, in pages */
public int length;
public PageRange(int start, int length) {
this.start = start;
this.length = length;
}
@Override
public String toString() {
return "start:" + start + " length:" + length;
}
}
/**
* @return position in pages
*/
public synchronized int allocatePages(long length) {
int required = (int) (length / MVStore.BLOCK_SIZE) + 1;
for (PageRange pr : freeSpaceList) {
if (pr.length >= required) {
return pr.start;
}
}
throw DataUtils.newIllegalStateException("could not find a free page to allocate");
}
public synchronized void markUsed(Chunk c) {
int chunkStart = (int) (c.start / MVStore.BLOCK_SIZE);
int required = (int) ((c.start + c.length) / MVStore.BLOCK_SIZE) + 2 - chunkStart;
PageRange found = null;
int i = 0;
for (PageRange pr : freeSpaceList) {
if (chunkStart >= pr.start && chunkStart < (pr.start + pr.length)) {
found = pr;
break;
}
i++;
}
if (found == null) {
throw DataUtils.newIllegalStateException("cannot find spot to mark chunk as used in free list, "
+ c.toString());
}
if (chunkStart + required > found.start + found.length) {
throw DataUtils.newIllegalStateException("chunk runs over edge of free space, " + c.toString());
}
if (found.start == chunkStart) {
// if the used-chunk is at the beginning of a free-space-range
found.start += required;
found.length -= required;
if (found.length == 0) {
// if the free-space-range is now empty, remove it
freeSpaceList.remove(i);
}
} else if (found.start + found.length == chunkStart + required) {
// if the used-chunk is at the end of a free-space-range
found.length -= required;
if (found.length == 0) {
// if the free-space-range is now empty, remove it
freeSpaceList.remove(i);
}
} else {
// it's in the middle, so split the existing entry
int length1 = chunkStart - found.start;
int start2 = chunkStart + required;
int length2 = found.start + found.length - chunkStart - required;
found.length = length1;
PageRange newRange = new PageRange(start2, length2);
freeSpaceList.add(i + 1, newRange);
}
}
public synchronized void markFree(Chunk c) {
int chunkStart = (int) (c.start / MVStore.BLOCK_SIZE);
int required = (c.length / MVStore.BLOCK_SIZE) + 1;
PageRange found = null;
int i = 0;
for (PageRange pr : freeSpaceList) {
if (pr.start > chunkStart) {
found = pr;
break;
}
i++;
}
if (found == null) {
throw DataUtils.newIllegalStateException("cannot find spot to mark chunk as unused in free list, "
+ c.toString());
}
if (chunkStart + required + 1 == found.start) {
// if the used-chunk is adjacent to the beginning of a
// free-space-range
found.start = chunkStart;
found.length += required;
// compactify-free-list: merge the previous entry into this one if
// they are now adjacent
if (i > 0) {
PageRange previous = freeSpaceList.get(i - 1);
if (previous.start + previous.length + 1 == found.start) {
previous.length += found.length;
freeSpaceList.remove(i);
}
}
return;
}
if (i > 0) {
// if the used-chunk is adjacent to the end of a free-space-range
PageRange previous = freeSpaceList.get(i - 1);
if (previous.start + previous.length + 1 == chunkStart) {
previous.length += required;
// compactify-free-list: merge the next entry into this one if
// they are now adjacent
if (previous.start + previous.length + 1 == found.start) {
previous.length += found.length;
freeSpaceList.remove(i);
}
return;
}
}
// it is between 2 entries, so add a new one
PageRange newRange = new PageRange(chunkStart, required);
freeSpaceList.add(i, newRange);
}
public synchronized void clear() {
freeSpaceList.clear();
freeSpaceList.add(new PageRange(FIRST_FREE_PAGE, MAX_NO_PAGES));
}
/** debug method */
public void dumpFreeList() {
StringBuilder buf = new StringBuilder("free list : ");
boolean first = true;
for (PageRange pr : freeSpaceList) {
if (first) {
first = false;
} else {
buf.append(", ");
}
buf.append(pr.start + "-" + (pr.start + pr.length - 1));
}
System.out.println(buf.toString());
}
}
......@@ -12,12 +12,12 @@ import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.h2.compress.CompressLZF;
import org.h2.compress.Compressor;
......@@ -148,6 +148,11 @@ public class MVStore {
*/
private final ConcurrentHashMap<Integer, Chunk> chunks =
new ConcurrentHashMap<Integer, Chunk>();
/**
* The list of free spaces between the chunks.
*/
private FreeSpaceList freeSpaceList = new FreeSpaceList();
/**
* The map of temporarily freed entries in the chunks. The key is the
......@@ -564,6 +569,14 @@ public class MVStore {
lastChunkId = Math.max(c.id, lastChunkId);
chunks.put(c.id, c);
}
// rebuild the free space list
freeSpaceList.clear();
for (Chunk c : chunks.values()) {
if (c.start == Long.MAX_VALUE) {
continue;
}
freeSpaceList.markUsed(c);
}
}
private void readFileHeader() {
......@@ -694,6 +707,7 @@ public class MVStore {
}
meta = null;
chunks.clear();
freeSpaceList.clear();
cache.clear();
maps.clear();
} catch (Exception e) {
......@@ -848,7 +862,7 @@ public class MVStore {
}
applyFreedChunks(storeVersion);
ArrayList<Integer> removedChunks = New.arrayList();
Set<Integer> removedChunks = New.hashSet();
// do it twice, because changing the meta table
// could cause a chunk to become empty
for (int i = 0; i < 2; i++) {
......@@ -911,11 +925,13 @@ public class MVStore {
// by an old version
// so empty space is not reused too early
for (int x : removedChunks) {
freeSpaceList.markFree(chunks.get(x));
chunks.remove(x);
}
c.start = filePos;
c.length = chunkLength;
freeSpaceList.markUsed(c);
c.metaRootPos = meta.getRoot().getPos();
buff.position(0);
c.writeHeader(buff);
......@@ -1036,33 +1052,7 @@ public class MVStore {
}
private long allocateChunk(long length) {
BitSet set = new BitSet();
set.set(0);
set.set(1);
for (Chunk c : chunks.values()) {
if (c.start == Long.MAX_VALUE) {
continue;
}
int first = (int) (c.start / BLOCK_SIZE);
int last = (int) ((c.start + c.length) / BLOCK_SIZE);
set.set(first, last + 2);
}
int required = (int) (length / BLOCK_SIZE) + 1;
for (int i = 0; i < set.size(); i++) {
if (!set.get(i)) {
boolean ok = true;
for (int j = 0; j < required; j++) {
if (set.get(i + j)) {
ok = false;
break;
}
}
if (ok) {
return i * BLOCK_SIZE;
}
}
}
return set.size() * BLOCK_SIZE;
return ((long) freeSpaceList.allocatePages(length)) * BLOCK_SIZE;
}
/**
......@@ -1500,6 +1490,7 @@ public class MVStore {
}
meta.clear();
chunks.clear();
freeSpaceList.clear();
maps.clear();
synchronized (freedChunks) {
freedChunks.clear();
......@@ -1530,6 +1521,7 @@ public class MVStore {
loadFromFile = true;
do {
last = chunks.remove(lastChunkId);
freeSpaceList.markFree(last);
lastChunkId--;
} while (last.version > version && chunks.size() > 0);
rootChunkStart = last.start;
......
......@@ -79,6 +79,10 @@ public class TestMVStore extends TestBase {
testIterate();
testCloseTwice();
testSimple();
// this test will run out of memory on a 32-bit VM before it gets to the part we need it to test
if ("64".equals(System.getProperty("sun.arch.data.model"))) {
testLargerThan2G();
}
}
private void testAtomicOperations() {
......@@ -1393,6 +1397,32 @@ public class TestMVStore extends TestBase {
s.close();
}
private void testLargerThan2G() {
String fileName = getBaseDir() + "/testLargerThan2G.h3";
FileUtils.delete(fileName);
MVStore store = new MVStore.Builder().cacheSize(800).fileName(fileName).writeBufferSize(0).writeDelay(-1)
.open();
MVMap<String, String> map = store.openMap("test");
int totalWrite = 15000000;
int lineStored = 0;
while (lineStored <= totalWrite) {
lineStored++;
String actualKey = lineStored + " just for length length length " + lineStored;
String value = "Just a a string that is kinda long long long but not too much much much much much much much much much "
+ lineStored;
map.put(actualKey, value);
if (lineStored % 1000000 == 0) {
store.store();
}
}
store.store();
store.close();
}
/**
* Open a store for the given file name, using a small page size.
*
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论