提交 570437e8 authored 作者: Thomas Mueller's avatar Thomas Mueller

Improved performance for read operations.

上级 44027adc
......@@ -12,13 +12,15 @@ import java.util.Iterator;
* A cursor to iterate over elements in ascending order.
*
* @param <K> the key type
* @param <V> the value type
*/
public class Cursor<K> implements Iterator<K> {
public class Cursor<K, V> implements Iterator<K> {
private final MVMap<K, ?> map;
private final K from;
private CursorPos pos;
private K current;
private V currentValue, lastValue;
private final Page root;
private boolean initialized;
......@@ -42,9 +44,19 @@ public class Cursor<K> implements Iterator<K> {
public K next() {
hasNext();
K c = current;
lastValue = currentValue;
fetchNext();
return c;
}
/**
* Get the last read value if there was one.
*
* @return the value or null
*/
public V getValue() {
return lastValue;
}
/**
* Skip over that many entries. This method is relatively fast (for this map
......@@ -110,7 +122,9 @@ public class Cursor<K> implements Iterator<K> {
private void fetchNext() {
while (pos != null) {
if (pos.index < pos.page.getKeyCount()) {
current = (K) pos.page.getKey(pos.index++);
int index = pos.index++;
current = (K) pos.page.getKey(index);
currentValue = (V) pos.page.getValue(index);
return;
}
pos = pos.parent;
......
......@@ -17,6 +17,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map;
import org.h2.engine.Constants;
import org.h2.util.New;
......@@ -852,5 +854,39 @@ public class DataUtils {
return errorValue;
}
}
/**
* An entry of a map.
*
* @param <K> the key type
* @param <V> the value type
*/
public static class MapEntry<K, V> implements Map.Entry<K, V> {
private final K key;
private V value;
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
throw DataUtils.newUnsupportedOperationException(
"Updating the value is not supported");
}
}
}
......@@ -745,20 +745,30 @@ public class MVMap<K, V> extends AbstractMap<K, V>
}
/**
* Iterate over all keys.
* Iterate over a number of keys.
*
* @param from the first key to return
* @return the iterator
*/
public Cursor<K> keyIterator(K from) {
return new Cursor<K>(this, root, from);
public Iterator<K> keyIterator(K from) {
return new Cursor<K, V>(this, root, from);
}
/**
* Get a cursor to iterate over a number of keys and values.
*
* @param from the first key to return
* @return the cursor
*/
public Cursor<K, V> cursor(K from) {
return new Cursor<K, V>(this, root, from);
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
HashMap<K, V> map = new HashMap<K, V>();
for (K k : keySet()) {
map.put(k, get(k));
for (Cursor<K, V> cursor = cursor(null); cursor.hasNext();) {
map.put(cursor.next(), cursor.getValue());
}
return map.entrySet();
}
......@@ -771,7 +781,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
@Override
public Iterator<K> iterator() {
return new Cursor<K>(map, root, null);
return new Cursor<K, V>(map, root, null);
}
@Override
......
......@@ -17,6 +17,7 @@ import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableFilter;
import org.h2.value.ValueLong;
/**
* An index that delegates indexing to another index.
......@@ -53,10 +54,10 @@ public class MVDelegateIndex extends BaseIndex {
@Override
public Cursor find(Session session, SearchRow first, SearchRow last) {
long min = mainIndex.getKey(first, Long.MIN_VALUE, Long.MIN_VALUE);
ValueLong min = mainIndex.getKey(first, MVPrimaryIndex.MIN, MVPrimaryIndex.MIN);
// ifNull is MIN_VALUE as well, because the column is never NULL
// so avoid returning all rows (returning one row is OK)
long max = mainIndex.getKey(last, Long.MAX_VALUE, Long.MIN_VALUE);
ValueLong max = mainIndex.getKey(last, MVPrimaryIndex.MAX, MVPrimaryIndex.MIN);
return mainIndex.find(session, min, max);
}
......
......@@ -9,6 +9,8 @@ package org.h2.mvstore.db;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import org.h2.constant.ErrorCode;
import org.h2.engine.Constants;
......@@ -18,6 +20,7 @@ import org.h2.index.BaseIndex;
import org.h2.index.Cursor;
import org.h2.index.IndexType;
import org.h2.message.DbException;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.mvstore.db.TransactionStore.TransactionMap;
import org.h2.result.Row;
......@@ -36,6 +39,10 @@ import org.h2.value.ValueNull;
*/
public class MVPrimaryIndex extends BaseIndex {
static final ValueLong MIN = ValueLong.get(Long.MIN_VALUE);
static final ValueLong MAX = ValueLong.get(Long.MAX_VALUE);
static final ValueLong ZERO = ValueLong.get(0);
private final MVTable mvTable;
private final String mapName;
private TransactionMap<Value, Value> dataMap;
......@@ -152,30 +159,30 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public Cursor find(Session session, SearchRow first, SearchRow last) {
long min, max;
ValueLong min, max;
if (first == null || mainIndexColumn < 0) {
min = Long.MIN_VALUE;
min = MIN;
} else {
Value v = first.getValue(mainIndexColumn);
ValueLong v = (ValueLong) first.getValue(mainIndexColumn);
if (v == null) {
min = 0;
min = ZERO;
} else {
min = v.getLong();
min = v;
}
}
if (last == null || mainIndexColumn < 0) {
max = Long.MAX_VALUE;
max = MAX;
} else {
Value v = last.getValue(mainIndexColumn);
ValueLong v = (ValueLong) last.getValue(mainIndexColumn);
if (v == null) {
max = Long.MAX_VALUE;
max = MAX;
} else {
max = v.getLong();
max = v;
}
}
TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.keyIterator(
ValueLong.get(min)), max);
return new MVStoreCursor(map.entryIterator(
min), max);
}
@Override
......@@ -196,7 +203,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) {
try {
long cost = 10 * (dataMap.map.sizeAsLong() + Constants.COST_ROW_OFFSET);
long cost = 10 * (dataMap.sizeAsLongEstimated() + Constants.COST_ROW_OFFSET);
return cost;
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED);
......@@ -236,15 +243,15 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public Cursor findFirstOrLast(Session session, boolean first) {
TransactionMap<Value, Value> map = getMap(session);
Value v = first ? map.firstKey() : map.lastKey();
ValueLong v = (ValueLong) (first ? map.firstKey() : map.lastKey());
if (v == null) {
return new MVStoreCursor(session, Collections.<Value>emptyList().iterator(), 0);
return new MVStoreCursor(Collections.<Entry<Value, Value>>emptyList().iterator(), null);
}
long key = v.getLong();
MVStoreCursor cursor = new MVStoreCursor(session,
Arrays.asList((Value) ValueLong.get(key)).iterator(), key);
cursor.next();
return cursor;
Value value = map.get(v);
Entry<Value, Value> e = new DataUtils.MapEntry<Value, Value>(v, value);
@SuppressWarnings("unchecked")
List<Entry<Value, Value>> list = Arrays.asList(e);
return new MVStoreCursor(list.iterator(), v);
}
@Override
......@@ -261,7 +268,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public long getRowCountApproximation() {
try {
return dataMap.map.sizeAsLong();
return dataMap.sizeAsLongEstimated();
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED);
}
......@@ -290,7 +297,7 @@ public class MVPrimaryIndex extends BaseIndex {
* @param ifNull the value to use if the column is NULL
* @return the key
*/
long getKey(SearchRow row, long ifEmpty, long ifNull) {
ValueLong getKey(SearchRow row, ValueLong ifEmpty, ValueLong ifNull) {
if (row == null) {
return ifEmpty;
}
......@@ -300,7 +307,7 @@ public class MVPrimaryIndex extends BaseIndex {
} else if (v == ValueNull.INSTANCE) {
return ifNull;
}
return v.getLong();
return (ValueLong) v.convertTo(Value.LONG);
}
/**
......@@ -311,9 +318,9 @@ public class MVPrimaryIndex extends BaseIndex {
* @param last the key of the last row
* @return the cursor
*/
Cursor find(Session session, long first, long last) {
Cursor find(Session session, ValueLong first, ValueLong last) {
TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.keyIterator(ValueLong.get(first)), last);
return new MVStoreCursor(map.entryIterator(first), last);
}
@Override
......@@ -340,14 +347,12 @@ public class MVPrimaryIndex extends BaseIndex {
*/
class MVStoreCursor implements Cursor {
private final Session session;
private final Iterator<Value> it;
private final long last;
private ValueLong current;
private final Iterator<Entry<Value, Value>> it;
private final ValueLong last;
private Entry<Value, Value> current;
private Row row;
public MVStoreCursor(Session session, Iterator<Value> it, long last) {
this.session = session;
public MVStoreCursor(Iterator<Entry<Value, Value>> it, ValueLong last) {
this.it = it;
this.last = last;
}
......@@ -356,7 +361,9 @@ public class MVPrimaryIndex extends BaseIndex {
public Row get() {
if (row == null) {
if (current != null) {
row = getRow(session, current.getLong());
ValueArray array = (ValueArray) current.getValue();
row = new Row(array.getList(), 0);
row.setKey(current.getKey().getLong());
}
}
return row;
......@@ -369,8 +376,8 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public boolean next() {
current = (ValueLong) it.next();
if (current != null && current.getLong() > last) {
current = it.next();
if (current != null && current.getKey().getLong() > last.getLong()) {
current = null;
}
row = null;
......
......@@ -66,7 +66,7 @@ public class MVSecondaryIndex extends BaseIndex {
ValueDataType valueType = new ValueDataType(null, null, null);
dataMap = mvTable.getTransaction(null).openMap(
mapName, keyType, valueType);
if (keyType != dataMap.map.getKeyType()) {
if (keyType != dataMap.getKeyType()) {
throw DbException.throwInternalError("Incompatible key type");
}
}
......@@ -102,6 +102,7 @@ public class MVSecondaryIndex extends BaseIndex {
}
if (indexType.isUnique()) {
// check if there is another (uncommitted) entry
// TODO use entry iterator
Iterator<Value> it = map.keyIterator(unique, true);
while (it.hasNext()) {
ValueArray k = (ValueArray) it.next();
......@@ -195,7 +196,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) {
try {
return 10 * getCostRangeIndex(masks, dataMap.map.sizeAsLong(), filter, sortOrder);
return 10 * getCostRangeIndex(masks, dataMap.sizeAsLongEstimated(), filter, sortOrder);
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED);
}
......@@ -244,7 +245,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public boolean needRebuild() {
try {
return dataMap.map.sizeAsLong() == 0;
return dataMap.sizeAsLongEstimated() == 0;
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED);
}
......@@ -259,7 +260,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public long getRowCountApproximation() {
try {
return dataMap.map.sizeAsLong();
return dataMap.sizeAsLongEstimated();
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED);
}
......
......@@ -282,7 +282,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex {
@Override
public boolean needRebuild() {
try {
return dataMap.map.sizeAsLong() == 0;
return dataMap.sizeAsLongEstimated() == 0;
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED);
}
......@@ -297,7 +297,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex {
@Override
public long getRowCountApproximation() {
try {
return dataMap.map.sizeAsLong();
return dataMap.sizeAsLongEstimated();
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED);
}
......
/*
* Copyright 2004-2013 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
*/
package org.h2.test.store;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import org.h2.mvstore.Chunk;
import org.h2.mvstore.MVStore;
import org.h2.mvstore.Page;
import org.h2.mvstore.db.TransactionStore;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
import org.h2.util.Profiler;
import org.h2.value.ValueLong;
import org.h2.value.ValueString;
/**
* Tests performance and helps analyze bottlenecks.
*/
public class TestBenchmark extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
test(true);
test(false);
test(true);
test(false);
test(true);
test(false);
}
private void test(boolean mvStore) throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true);
Connection conn;
Statement stat;
String url = "mvstore";
if (mvStore) {
url += ";MV_STORE=TRUE;LOG=0";
}
// 2033 mvstore
// 2313 (2075?) default
url = getURL(url, true);
conn = getConnection(url);
stat = conn.createStatement();
stat.execute("create table test(id bigint primary key, name varchar)");
conn.setAutoCommit(false);
PreparedStatement prep = conn.prepareStatement("insert into test values(?, ?)");
String data = "Hello World";
int rowCount = 100000;
int readCount = 20* rowCount;
for (int i = 0; i < rowCount; i++) {
prep.setInt(1, i);
prep.setString(2, data);
prep.execute();
if (i % 100 == 0) {
conn.commit();
}
}
long start = System.currentTimeMillis();
// Profiler prof = new Profiler();
// prof.sumClasses=true;
// prof.startCollecting();
;
prep = conn.prepareStatement("select * from test where id = ?");
for (int i = 0; i < readCount; i++) {
prep.setInt(1, i % rowCount);
prep.executeQuery();
}
//System.out.println("Transactionstore.counter " + ValueLong.counter);
//System.out.println("count " + Page.writeCount + " avgLen " + (1.0*Page.writeLength/Page.writeCount) + " avgSize " + (1.0*Page.writeSize/Page.writeCount));
//System.out.println(prof.getTop(5));
//System.out.println("ountUnc:" + counterUnc);
//System.out.println("ount:" + counter);
System.out.println((System.currentTimeMillis() - start) + " " + (mvStore ? "mvstore" : "default"));
conn.close();
// MVStore s = new MVStore.Builder().fileName(getBaseDir() + "/mvstore.mv.db").open();
// int count = 0;
// long length = 0;
// for(String k : s.getMetaMap().keyList()) {
// if (k.startsWith("chunk.")) {
// String x = s.getMetaMap().get(k);
// Chunk c = Chunk.fromString(x);
// if (c.length < Integer.MAX_VALUE) {
// count++;
// length += c.length;
// }
// }
// }
// if (count > 0) {
// System.out.println("chunks: " + count + " average length: " + (length / count));
// }
// s.close();
}
}
......@@ -706,7 +706,7 @@ public class TestMVStore extends TestBase {
map.put(i, 10 * i);
}
Cursor<Integer> c = map.keyIterator(50);
Cursor<Integer, Integer> c = map.cursor(50);
// skip must reset the root of the cursor
c.skip(10);
for (int i = 70; i < 100; i += 2) {
......@@ -732,7 +732,7 @@ public class TestMVStore extends TestBase {
}
}
// skip
c = map.keyIterator(0);
c = map.cursor(0);
assertTrue(c.hasNext());
assertEquals(0, c.next().intValue());
c.skip(0);
......@@ -742,11 +742,11 @@ public class TestMVStore extends TestBase {
c.skip(20);
assertEquals(48, c.next().intValue());
c = map.keyIterator(0);
c = map.cursor(0);
c.skip(20);
assertEquals(40, c.next().intValue());
c = map.keyIterator(0);
c = map.cursor(0);
assertEquals(0, c.next().intValue());
assertEquals(12, map.keyList().indexOf(24));
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论