提交 001f9d04 authored 作者: Thomas Mueller's avatar Thomas Mueller

The MultiDimension tools was extended with a few helper methods. The API was unified a bit.

上级 725532f8
...@@ -18,7 +18,9 @@ Change Log ...@@ -18,7 +18,9 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>ODBC: additional connection settings can now be added to the database name. <ul><li>The MultiDimension tools was extended with a few helper methods.
The API was unified a bit.
</li><li>ODBC: additional connection settings can now be added to the database name.
Example: ~/test;cipher=xtea. Therefore, encrypted databases are supported. Example: ~/test;cipher=xtea. Therefore, encrypted databases are supported.
</li><li>Local temporary tables can now be created without having to commit a transaction </li><li>Local temporary tables can now be created without having to commit a transaction
using CREATE LOCAL TEMPORARY TABLE TEMP(ID INT PRIMARY KEY) TRANSACTIONAL. using CREATE LOCAL TEMPORARY TABLE TEMP(ID INT PRIMARY KEY) TRANSACTIONAL.
......
...@@ -37,65 +37,107 @@ public class MultiDimension implements Comparator<long[]> { ...@@ -37,65 +37,107 @@ public class MultiDimension implements Comparator<long[]> {
return INSTANCE; return INSTANCE;
} }
/**
* Normalize a value so that it is between the minimum and maximum value for
* the given number of dimensions.
*
* @param dimensions the number of dimensions
* @param value the value (must be in the range min..max)
* @param min the minimum value
* @param max the maximum value (must be larger than min)
* @return the normalized value in the range 0..getMaxValue(dimensions)
*/
public int normalize(int dimensions, double value, double min, double max) {
if (value < min || value > max) {
throw new IllegalArgumentException(min + "<" + value + "<" + max);
}
double x = (value - min) / (max - min);
return (int) (x * getMaxValue(dimensions));
}
/**
* Get the maximum value for the given dimension count. For two dimensions,
* each value must contain at most 32 bit, for 3: 21 bit, 4: 16 bit, 5: 12
* bit, 6: 10 bit, 7: 9 bit, 8: 8 bit.
*
* @param dimensions the number of dimensions
* @return the maximum value
*/
public int getMaxValue(int dimensions) {
if (dimensions < 2 || dimensions > 32) {
throw new IllegalArgumentException("" + dimensions);
}
int bitsPerValue = getBitsPerValue(dimensions);
return (int) ((1L << bitsPerValue) - 1);
}
private int getBitsPerValue(int dimensions) {
return Math.min(31, 64 / dimensions);
}
/** /**
* Convert the multi-dimensional value into a one-dimensional (scalar) value. * Convert the multi-dimensional value into a one-dimensional (scalar) value.
* This is done by interleaving the bits of the values. * This is done by interleaving the bits of the values.
* Each values must be bigger or equal to 0. The maximum value * Each values must be between 0 (including) and the maximum value
* depends on the number of dimensions. For two keys, it is 32 bit, * for the given number of dimensions (getMaxValue, excluding).
* for 3: 21 bit, 4: 16 bit, 5: 12 bit, 6: 10 bit, 7: 9 bit, 8: 8 bit. * To normalize values to this range, use the normalize function.
* *
* @param values the multi-dimensional value * @param values the multi-dimensional value
* @return the scalar value * @return the scalar value
*/ */
public long interleave(int[] values) { public long interleave(int... values) {
int dimensions = values.length; int dimensions = values.length;
int bitsPerValue = 64 / dimensions;
// for 2 keys: 0x800000; 3: 0x
long max = getMaxValue(dimensions); long max = getMaxValue(dimensions);
int bitsPerValue = getBitsPerValue(dimensions);
long x = 0; long x = 0;
for (int i = 0; i < dimensions; i++) { for (int i = 0; i < dimensions; i++) {
long k = values[i]; long k = values[i];
if (k < 0 || k >= max) { if (k < 0 || k > max) {
throw new IllegalArgumentException("value out of range; value=" + values[i] + " min=0 max=" + max); throw new IllegalArgumentException(0 + "<" + k + "<" + max);
} }
for (int b = 0; b < bitsPerValue; b++) { for (int b = 0; b < bitsPerValue; b++) {
x |= (k & (1L << b)) << (i + (dimensions - 1) * b); x |= (k & (1L << b)) << (i + (dimensions - 1) * b);
} }
} }
if (dimensions == 2) {
long xx = getMorton2(values[0], values[1]);
if (xx != x) {
throw new IllegalArgumentException("test");
}
}
return x; return x;
} }
/** /**
* Get the maximum value for the given dimension count * Convert the two-dimensional value into a one-dimensional (scalar) value.
* @param dimensions the number of dimensions * This is done by interleaving the bits of the values.
* Each values must be between 0 (including) and the maximum value
* for the given number of dimensions (getMaxValue, excluding).
* To normalize values to this range, use the normalize function.
* *
* @return the maximum value * @param x the value of the first dimension, normalized
* @param y the value of the second dimension, normalized
* @return the scalar value
*/ */
public static long getMaxValue(int dimensions) { public long interleave(int x, int y) {
if (dimensions < 2 || dimensions > 64) { if (x < 0) {
throw new IllegalArgumentException("dimensions: " + dimensions); throw new IllegalArgumentException(0 + "<" + x);
}
if (y < 0) {
throw new IllegalArgumentException(0 + "<" + y);
} }
int bitsPerValue = 64 / dimensions; long z = 0;
// for 2 keys: 0x800000; 3: 0x for (int i = 0; i < 32; i++) {
return 1L << bitsPerValue; z |= (x & (1L << i)) << i;
z |= (y & (1L << i)) << (i + 1);
}
return z;
} }
/** /**
* Gets one of the original multi-dimensional values from a scalar value. * Gets one of the original multi-dimensional values from a scalar value.
* *
* @param scalar the scalar value
* @param dimensions the number of dimensions * @param dimensions the number of dimensions
* @param scalar the scalar value
* @param dim the dimension of the returned value (starting from 0) * @param dim the dimension of the returned value (starting from 0)
* @return the value * @return the value
*/ */
public int deinterleave(long scalar, int dimensions, int dim) { public int deinterleave(int dimensions, long scalar, int dim) {
int bitsPerValue = 64 / dimensions; int bitsPerValue = getBitsPerValue(dimensions);
int value = 0; int value = 0;
for (int i = 0; i < bitsPerValue; i++) { for (int i = 0; i < bitsPerValue; i++) {
value |= (scalar >> (dim + (dimensions - 1) * i)) & (1L << i); value |= (scalar >> (dim + (dimensions - 1) * i)) & (1L << i);
...@@ -103,14 +145,6 @@ public class MultiDimension implements Comparator<long[]> { ...@@ -103,14 +145,6 @@ public class MultiDimension implements Comparator<long[]> {
return value; return value;
} }
// public static int get(long z, int d) {
// int n = 0;
// for (int i = 0; i < 31; i++) {
// n |= (z & (1 << (i + i + d))) >> (i + d);
// }
// return n;
// }
/** /**
* Generates an optimized multi-dimensional range query. * Generates an optimized multi-dimensional range query.
* The query contains parameters. It can only be used with the H2 database. * The query contains parameters. It can only be used with the H2 database.
...@@ -172,7 +206,7 @@ public class MultiDimension implements Comparator<long[]> { ...@@ -172,7 +206,7 @@ public class MultiDimension implements Comparator<long[]> {
private long[][] getMortonRanges(int[] min, int[] max) { private long[][] getMortonRanges(int[] min, int[] max) {
int len = min.length; int len = min.length;
if (max.length != len) { if (max.length != len) {
throw new IllegalArgumentException("dimensions mismatch"); throw new IllegalArgumentException(len + "=" + max.length);
} }
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
if (min[i] > max[i]) { if (min[i] > max[i]) {
...@@ -184,21 +218,12 @@ public class MultiDimension implements Comparator<long[]> { ...@@ -184,21 +218,12 @@ public class MultiDimension implements Comparator<long[]> {
int total = getSize(min, max, len); int total = getSize(min, max, len);
ArrayList<long[]> list = New.arrayList(); ArrayList<long[]> list = New.arrayList();
addMortonRanges(list, min, max, len, 0); addMortonRanges(list, min, max, len, 0);
optimize(list, total); combineEntries(list, total);
long[][] ranges = new long[list.size()][2]; long[][] ranges = new long[list.size()][2];
list.toArray(ranges); list.toArray(ranges);
return ranges; return ranges;
} }
private long getMorton2(int x, int y) {
long z = 0;
for (int i = 0; i < 32; i++) {
z |= (x & (1L << i)) << i;
z |= (y & (1L << i)) << (i + 1);
}
return z;
}
private int getSize(int[] min, int[] max, int len) { private int getSize(int[] min, int[] max, int len) {
int size = 1; int size = 1;
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
...@@ -208,9 +233,15 @@ public class MultiDimension implements Comparator<long[]> { ...@@ -208,9 +233,15 @@ public class MultiDimension implements Comparator<long[]> {
return size; return size;
} }
private void optimize(ArrayList<long[]> list, int total) { /**
* Combine entries if the size of the list is too large.
*
* @param list the size of the list
* @param total
*/
private void combineEntries(ArrayList<long[]> list, int total) {
Collections.sort(list, this); Collections.sort(list, this);
for (int minGap = 10;; minGap += minGap / 2) { for (int minGap = 10; minGap < total; minGap += minGap / 2) {
for (int i = 0; i < list.size() - 1; i++) { for (int i = 0; i < list.size() - 1; i++) {
long[] current = list.get(i); long[] current = list.get(i);
long[] next = list.get(i + 1); long[] next = list.get(i + 1);
...@@ -224,7 +255,7 @@ public class MultiDimension implements Comparator<long[]> { ...@@ -224,7 +255,7 @@ public class MultiDimension implements Comparator<long[]> {
for (long[] range : list) { for (long[] range : list) {
searched += range[1] - range[0] + 1; searched += range[1] - range[0] + 1;
} }
if (searched > 2 * total || list.size() < 3 /* || minGap > total */) { if (searched > 2 * total || list.size() < 100) {
break; break;
} }
} }
...@@ -236,18 +267,18 @@ public class MultiDimension implements Comparator<long[]> { ...@@ -236,18 +267,18 @@ public class MultiDimension implements Comparator<long[]> {
private void addMortonRanges(ArrayList<long[]> list, int[] min, int[] max, int len, int level) { private void addMortonRanges(ArrayList<long[]> list, int[] min, int[] max, int len, int level) {
if (level > 100) { if (level > 100) {
throw new IllegalArgumentException("Stop"); throw new IllegalArgumentException("" + level);
} }
int largest = 0, largestDiff = 0; int largest = 0, largestDiff = 0;
long size = 1; long size = 1;
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
int diff = max[i] - min[i]; int diff = max[i] - min[i];
if (diff < 0) { if (diff < 0) {
throw new IllegalArgumentException("Stop"); throw new IllegalArgumentException(""+ diff);
} }
size *= diff + 1; size *= diff + 1;
if (size < 0) { if (size < 0) {
throw new IllegalArgumentException("Stop"); throw new IllegalArgumentException("" + size);
} }
if (diff > largestDiff) { if (diff > largestDiff) {
largestDiff = diff; largestDiff = diff;
...@@ -256,7 +287,7 @@ public class MultiDimension implements Comparator<long[]> { ...@@ -256,7 +287,7 @@ public class MultiDimension implements Comparator<long[]> {
} }
long low = interleave(min), high = interleave(max); long low = interleave(min), high = interleave(max);
if (high < low) { if (high < low) {
throw new IllegalArgumentException("Stop"); throw new IllegalArgumentException(high + "<" + low);
} }
long range = high - low + 1; long range = high - low + 1;
if (range == size) { if (range == size) {
...@@ -294,7 +325,7 @@ public class MultiDimension implements Comparator<long[]> { ...@@ -294,7 +325,7 @@ public class MultiDimension implements Comparator<long[]> {
scale--; scale--;
int m = roundUp(a + 2, 1 << scale) - 1; int m = roundUp(a + 2, 1 << scale) - 1;
if (m <= a || m >= b) { if (m <= a || m >= b) {
throw new IllegalArgumentException("stop"); throw new IllegalArgumentException(a + "<" + m + "<" + b);
} }
return m; return m;
} }
......
...@@ -27,20 +27,173 @@ public class TestMultiDimension extends TestBase { ...@@ -27,20 +27,173 @@ public class TestMultiDimension extends TestBase {
* @param a ignored * @param a ignored
*/ */
public static void main(String... a) throws Exception { public static void main(String... a) throws Exception {
TestBase.createCaller().init().test(); TestBase test = TestBase.createCaller().init();
test.config.traceTest = true;
test.test();
} }
public void test() throws SQLException { public void test() throws SQLException {
Random rand = new Random(10); testHelperMethods();
testPerformance2d();
testPerformance3d();
}
private void testHelperMethods() {
MultiDimension m = MultiDimension.getInstance();
assertEquals(Integer.MAX_VALUE, m.getMaxValue(2));
assertEquals(0, m.normalize(2, 0, 0, 100));
assertEquals(Integer.MAX_VALUE / 2, m.normalize(2, 50, 0, 100));
assertEquals(Integer.MAX_VALUE, m.normalize(2, 100, 0, 100));
assertEquals(Integer.MAX_VALUE / 10, m.normalize(2, 0.1, 0, 1));
assertEquals(0, m.normalize(2, 1, 1, 1));
assertEquals(0, m.normalize(2, 0, 0, 0));
assertEquals(3, m.interleave(1, 1));
assertEquals(3, m.interleave(new int[]{1, 1}));
assertEquals(5, m.interleave(3, 0));
assertEquals(5, m.interleave(new int[]{3, 0}));
assertEquals(10, m.interleave(0, 3));
assertEquals(10, m.interleave(new int[]{0, 3}));
long v = ((long) Integer.MAX_VALUE | ((long) Integer.MAX_VALUE << 31L));
assertEquals(v, m.interleave(Integer.MAX_VALUE, Integer.MAX_VALUE));
assertEquals(v, m.interleave(new int[] {Integer.MAX_VALUE, Integer.MAX_VALUE }));
Random random = new Random(1);
for (int i=0; i<1000; i++) {
int x = random.nextInt(Integer.MAX_VALUE), y = random.nextInt(Integer.MAX_VALUE);
v = m.interleave(new int[] {x, y});
long v2 = m.interleave(x, y);
assertEquals(v, v2);
int x1 = m.deinterleave(2, v, 0);
int y1 = m.deinterleave(2, v, 1);
assertEquals(x, x1);
assertEquals(y, y1);
}
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
int x = rand.nextInt(1000), y = rand.nextInt(1000), z = rand.nextInt(1000); int x = random.nextInt(1000), y = random.nextInt(1000), z = random.nextInt(1000);
MultiDimension tool = MultiDimension.getInstance(); MultiDimension tool = MultiDimension.getInstance();
long xyz = tool.interleave(new int[] { x, y, z }); long xyz = tool.interleave(new int[] { x, y, z });
assertEquals(x, tool.deinterleave(xyz, 3, 0)); assertEquals(x, tool.deinterleave(3, xyz, 0));
assertEquals(y, tool.deinterleave(xyz, 3, 1)); assertEquals(y, tool.deinterleave(3, xyz, 1));
assertEquals(z, tool.deinterleave(xyz, 3, 2)); assertEquals(z, tool.deinterleave(3, xyz, 2));
}
try {
m.getMaxValue(1);
fail();
} catch (IllegalArgumentException e) {
// expected
}
try {
m.getMaxValue(33);
fail();
} catch (IllegalArgumentException e) {
// expected
}
try {
m.normalize(2, 10, 11, 12);
fail();
} catch (IllegalArgumentException e) {
// expected
}
try {
m.normalize(2, 5, 10, 0);
fail();
} catch (IllegalArgumentException e) {
// expected
}
try {
m.normalize(2, 10, 0, 9);
fail();
} catch (IllegalArgumentException e) {
// expected
}
try {
m.interleave(-1, 5);
fail();
} catch (IllegalArgumentException e) {
// expected
}
try {
m.interleave(5, -1);
fail();
} catch (IllegalArgumentException e) {
// expected
}
try {
m.interleave(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
fail();
} catch (IllegalArgumentException e) {
// expected
}
} }
private void testPerformance2d() throws SQLException {
deleteDb("multiDimension");
Connection conn;
conn = getConnection("multiDimension");
Statement stat = conn.createStatement();
stat.execute("CREATE ALIAS MAP FOR \"" + getClass().getName() + ".interleave\"");
stat.execute("CREATE TABLE TEST(X INT NOT NULL, Y INT NOT NULL, " +
"XY BIGINT AS MAP(X, Y), DATA VARCHAR)");
stat.execute("CREATE INDEX IDX_X ON TEST(X, Y)");
stat.execute("CREATE INDEX IDX_XY ON TEST(XY)");
PreparedStatement prep = conn.prepareStatement(
"INSERT INTO TEST(X, Y, DATA) VALUES(?, ?, ?)");
// the MultiDimension tool is faster for 4225 (65^2) points
// the more the bigger the difference
int max = getSize(30, 65);
long time = System.currentTimeMillis();
for (int x = 0; x < max; x++) {
for (int y = 0; y < max; y++) {
long t2 = System.currentTimeMillis();
if (t2 - time > 1000) {
int percent = (int) (100.0 * ((double) x * max + y) / ((double) max * max));
trace(percent + "%");
time = t2;
}
prep.setInt(1, x);
prep.setInt(2, y);
prep.setString(3, "Test data");
prep.execute();
}
}
stat.execute("ANALYZE SAMPLE_SIZE 10000");
PreparedStatement prepRegular = conn.prepareStatement(
"SELECT * FROM TEST WHERE X BETWEEN ? AND ? " +
"AND Y BETWEEN ? AND ? ORDER BY X, Y");
MultiDimension multi = MultiDimension.getInstance();
String sql = multi.generatePreparedQuery("TEST", "XY", new String[] { "X", "Y" });
sql += " ORDER BY X, Y";
PreparedStatement prepMulti = conn.prepareStatement(sql);
long timeMulti = 0, timeRegular = 0;
int timeMax = getSize(500, 2000);
Random rand = new Random(1);
for (int i = 0; timeMulti < timeMax; i++) {
int size = rand.nextInt(max / 10);
int minX = rand.nextInt(max - size);
int minY = rand.nextInt(max - size);
int maxX = minX + size, maxY = minY + size;
time = System.currentTimeMillis();
ResultSet rs1 = multi.getResult(prepMulti, new int[] { minX, minY }, new int[] { maxX, maxY });
timeMulti += System.currentTimeMillis() - time;
time = System.currentTimeMillis();
prepRegular.setInt(1, minX);
prepRegular.setInt(2, maxX);
prepRegular.setInt(3, minY);
prepRegular.setInt(4, maxY);
ResultSet rs2 = prepRegular.executeQuery();
timeRegular += System.currentTimeMillis() - time;
while (rs1.next()) {
assertTrue(rs2.next());
assertEquals(rs1.getInt(1), rs2.getInt(1));
assertEquals(rs1.getInt(2), rs2.getInt(2));
}
assertFalse(rs2.next());
}
conn.close();
deleteDb("multiDimension");
trace("2d: regular: " + timeRegular + " MultiDimension: " + timeMulti);
}
private void testPerformance3d() throws SQLException {
deleteDb("multiDimension"); deleteDb("multiDimension");
Connection conn; Connection conn;
conn = getConnection("multiDimension"); conn = getConnection("multiDimension");
...@@ -52,8 +205,8 @@ public class TestMultiDimension extends TestBase { ...@@ -52,8 +205,8 @@ public class TestMultiDimension extends TestBase {
stat.execute("CREATE INDEX IDX_XYZ ON TEST(XYZ)"); stat.execute("CREATE INDEX IDX_XYZ ON TEST(XYZ)");
PreparedStatement prep = conn.prepareStatement( PreparedStatement prep = conn.prepareStatement(
"INSERT INTO TEST(X, Y, Z, DATA) VALUES(?, ?, ?, ?)"); "INSERT INTO TEST(X, Y, Z, DATA) VALUES(?, ?, ?, ?)");
// a reasonable max value to see the performance difference is 60; the // the MultiDimension tool is faster for 8000 (20^3) points
// higher the bigger the difference // the more the bigger the difference
int max = getSize(10, 20); int max = getSize(10, 20);
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
for (int x = 0; x < max; x++) { for (int x = 0; x < max; x++) {
...@@ -61,14 +214,9 @@ public class TestMultiDimension extends TestBase { ...@@ -61,14 +214,9 @@ public class TestMultiDimension extends TestBase {
for (int z = 0; z < max; z++) { for (int z = 0; z < max; z++) {
long t2 = System.currentTimeMillis(); long t2 = System.currentTimeMillis();
if (t2 - time > 1000) { if (t2 - time > 1000) {
int percent = (int) (100.0 * ((double) x * x * x) / ((double) max * max * max)); int percent = (int) (100.0 * ((double) x * max + y) / ((double) max * max));
trace(percent + "%"); trace(percent + "%");
time = t2; time = t2;
try {
Thread.sleep(10);
} catch (Exception e) {
// ignore
}
} }
prep.setInt(1, x); prep.setInt(1, x);
prep.setInt(2, y); prep.setInt(2, y);
...@@ -87,7 +235,8 @@ public class TestMultiDimension extends TestBase { ...@@ -87,7 +235,8 @@ public class TestMultiDimension extends TestBase {
sql += " ORDER BY X, Y, Z"; sql += " ORDER BY X, Y, Z";
PreparedStatement prepMulti = conn.prepareStatement(sql); PreparedStatement prepMulti = conn.prepareStatement(sql);
long timeMulti = 0, timeRegular = 0; long timeMulti = 0, timeRegular = 0;
int timeMax = getSize(100, 2000); int timeMax = getSize(500, 2000);
Random rand = new Random(1);
for (int i = 0; timeMulti < timeMax; i++) { for (int i = 0; timeMulti < timeMax; i++) {
int size = rand.nextInt(max / 10); int size = rand.nextInt(max / 10);
int minX = rand.nextInt(max - size); int minX = rand.nextInt(max - size);
...@@ -115,6 +264,18 @@ public class TestMultiDimension extends TestBase { ...@@ -115,6 +264,18 @@ public class TestMultiDimension extends TestBase {
} }
conn.close(); conn.close();
deleteDb("multiDimension"); deleteDb("multiDimension");
trace("3d: regular: " + timeRegular + " MultiDimension: " + timeMulti);
}
/**
* This method is called via reflection from the database.
*
* @param x the x value
* @param y the y value
* @return the bit-interleaved value
*/
public static long interleave(int x, int y) {
return MultiDimension.getInstance().interleave(x, y);
} }
/** /**
...@@ -128,4 +289,5 @@ public class TestMultiDimension extends TestBase { ...@@ -128,4 +289,5 @@ public class TestMultiDimension extends TestBase {
public static long interleave(int x, int y, int z) { public static long interleave(int x, int y, int z) {
return MultiDimension.getInstance().interleave(new int[] { x, y, z }); return MultiDimension.getInstance().interleave(new int[] { x, y, z });
} }
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论