package com.jwetherell.algorithms.data_structures.timing;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Formatter;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import junit.framework.Assert;
import com.jwetherell.algorithms.data_structures.KdTree;
import com.jwetherell.algorithms.data_structures.QuadTree;
public class SpatialDataStructuresTiming {
private static final int NUMBER_OF_TESTS = 3; // There will always be NUMBER_OF_TESTS+1 runs since the first round is thrown away (JITing)
private static final Random RANDOM = new Random();
private static final int ARRAY_SIZE = 1024*10;
private static final int RANDOM_SIZE = 1000 * ARRAY_SIZE;
private static final DecimalFormat FORMAT = new DecimalFormat("0.##");
private static final int TESTS = 3;
private static final String[] testNames = new String[TESTS]; // Array to hold the test names
private static final long[][] testResults = new long[TESTS][]; // Array to hold the test results
private static int[][] data = null;
private static String stringifiedData = null;
public static void main(String[] args) {
System.out.println("Starting tests.");
for (int i=0; i<NUMBER_OF_TESTS+1; i++)
runTests(i);
}
private static String generateTestData(int size, int[][] unsorteds) {
System.out.println("Generating data.");
StringBuilder builder = new StringBuilder();
builder.append("Array=");
for (int i = 0; i < size; i++) {
Integer j = RANDOM.nextInt(RANDOM_SIZE);
Integer k = RANDOM.nextInt(RANDOM_SIZE);
unsorteds[i] = new int[]{j,k};
if (i!=size-1)
builder.append(j).append(',').append(k).append(' ');
}
System.out.println("Generated data.");
return builder.toString();
}
private static void runTests(int round) {
data = new int[ARRAY_SIZE][ARRAY_SIZE];
stringifiedData = generateTestData(ARRAY_SIZE, data);
System.out.println();
int test = 0;
testKdTree("KdTree", round, test++, data);
DataStructuresTiming.putOutTheGarbage();
System.out.println();
QuadTree<QuadTree.XYPoint> prTree = new QuadTree.PointRegionQuadTree<QuadTree.XYPoint>(0,0,RANDOM_SIZE,RANDOM_SIZE);
testQuadTree("PointRegionQuadTree", round, test++, 100, data, prTree);
DataStructuresTiming.putOutTheGarbage();
System.out.println();
QuadTree<QuadTree.AxisAlignedBoundingBox> aaTree = new QuadTree.MxCifQuadTree<QuadTree.AxisAlignedBoundingBox>(0,0,RANDOM_SIZE,RANDOM_SIZE,10000,10000);
testQuadTree("MxCifQuadTree", round, test++, 100, data, aaTree);
DataStructuresTiming.putOutTheGarbage();
System.out.println();
if (round!=0) {
// Skip first round to avoid JIT
System.out.println(getTestResults(round, testNames, testResults));
}
}
private static boolean testKdTree(String name, int testRound, int testNum, int[][] unsorteds) {
if (testRound != 0) {
// Skip first round to avoid JIT
testNames[testNum] = name;
if (testResults[testNum] == null)
testResults[testNum] = new long[5];
}
int test = 0;
List<KdTree.XYZPoint> points = new ArrayList<KdTree.XYZPoint>(ARRAY_SIZE);
for (int i=0; i<ARRAY_SIZE; i++) {
KdTree.XYZPoint p = new KdTree.XYZPoint(unsorteds[i][0],unsorteds[i][1]);
points.add(p);
}
// init
long beforeMemory = DataStructuresTiming.getMemoryUse();
long beforeAddTime = System.nanoTime();
KdTree<KdTree.XYZPoint> tree = new KdTree<KdTree.XYZPoint>(points, 2); // 2 since we only care about two dimensions (x,y)
long afterAddTime = System.nanoTime();
long afterMemory = DataStructuresTiming.getMemoryUse();
long memory = afterMemory - beforeMemory;
if (testRound != 0)
testResults[testNum][test++] += memory;
System.out.println(name+" memory use = " + (memory / ARRAY_SIZE) + " bytes");
long addTime = afterAddTime - beforeAddTime;
if (testRound != 0)
testResults[testNum][test++] += addTime;
System.out.println(name + " add time = " + (addTime / ARRAY_SIZE) + " ns");
// contains
long beforeContainsTime = System.nanoTime();
for (KdTree.XYZPoint p : points) {
boolean r = tree.contains(p);
assertTrue("Point not found.", p, tree, r==true);
}
long afterContainsTime = System.nanoTime();
long containsTime = afterContainsTime - beforeContainsTime;
if (testRound != 0)
testResults[testNum][test++] += containsTime;
System.out.println(name + " contains time = " + (containsTime / ARRAY_SIZE) + " ns");
// nearest neighbor
long beforeNnTime = System.nanoTime();
for (KdTree.XYZPoint p : points) {
Collection<KdTree.XYZPoint> c = tree.nearestNeighbourSearch(4, p);
assertTrue("nearest neighbor didn't find anyone.", p, tree, c.size()>0);
}
long afterNnTime = System.nanoTime();
long nnTime = afterNnTime - beforeNnTime;
if (testRound != 0)
testResults[testNum][test++] += nnTime;
System.out.println(name + " nearest neighbor time = " + (nnTime / ARRAY_SIZE) + " ns");
// remove
long beforeRemovesTime = System.nanoTime();
for (KdTree.XYZPoint p : points) {
boolean r = tree.remove(p);
assertTrue("Point not removed.", p, tree, r==true);
}
long afterRemovesTime = System.nanoTime();
long removesTime = afterRemovesTime - beforeRemovesTime;
if (testRound != 0)
testResults[testNum][test++] += removesTime;
System.out.println(name + " removes time = " + (removesTime / ARRAY_SIZE) + " ns");
return true;
}
private static <Q extends QuadTree.XYPoint> boolean testQuadTree(String name, int testRound, int testNum, int range, int[][] unsorteds, QuadTree<Q> tree) {
if (testRound != 0) {
// Skip first round to avoid JIT
testNames[testNum] = name;
if (testResults[testNum] == null)
testResults[testNum] = new long[5];
}
int test = 0;
List<QuadTree.XYPoint> points = new ArrayList<QuadTree.XYPoint>(ARRAY_SIZE);
for (int i=0; i<ARRAY_SIZE; i++) {
QuadTree.XYPoint p = new QuadTree.XYPoint(unsorteds[i][0],unsorteds[i][1]);
points.add(p);
}
long beforeMemory = DataStructuresTiming.getMemoryUse();
long beforeAddTime = System.nanoTime();
for (QuadTree.XYPoint p : points)
tree.insert(p.getX(), p.getY());
long afterAddTime = System.nanoTime();
long afterMemory = DataStructuresTiming.getMemoryUse();
long memory = afterMemory - beforeMemory;
if (testRound != 0)
testResults[testNum][test++] += memory;
System.out.println(name+" memory use = " + (memory / ARRAY_SIZE) + " bytes");
long addTime = afterAddTime - beforeAddTime;
if (testRound != 0)
testResults[testNum][test++] += addTime;
System.out.println(name + " add time = " + (addTime / ARRAY_SIZE) + " ns");
// contains
long beforeContainsTime = System.nanoTime();
for (QuadTree.XYPoint p : points) {
Collection<Q> l = tree.queryRange(p.getX(), p.getY(), 1, 1); // looking for a single point
assertTrue("Point not found.", p, tree, l.size()>0);
}
long afterContainsTime = System.nanoTime();
long containsTime = afterContainsTime - beforeContainsTime;
if (testRound != 0)
testResults[testNum][test++] += containsTime;
System.out.println(name + " contains time = " + (containsTime / ARRAY_SIZE) + " ns");
// query range
long beforeQrTime = System.nanoTime();
for (QuadTree.XYPoint p : points) {
Collection<Q> l = tree.queryRange(p.getX(), p.getY(), range, range);
assertTrue("Range query returned no values.", p, tree, l.size()>0);
}
long afterQrTime = System.nanoTime();
long qrTime = afterQrTime - beforeQrTime;
if (testRound != 0)
testResults[testNum][test++] += qrTime;
System.out.println(name + " query range time = " + (qrTime / ARRAY_SIZE) + " ns");
// remove
long beforeRemovesTime = System.nanoTime();
for (QuadTree.XYPoint p : points) {
boolean r = tree.remove(p.getX(), p.getY());
assertTrue("Point not removed.", p, tree, r==true);
}
long afterRemovesTime = System.nanoTime();
long removesTime = afterRemovesTime - beforeRemovesTime;
if (testRound != 0)
testResults[testNum][test++] += removesTime;
System.out.println(name + " removes time = " + (removesTime / ARRAY_SIZE) + " ns");
return true;
}
private static final String getTestResults(int number, String[] names, long[][] results) {
StringBuilder resultsBuilder = new StringBuilder();
String format = "%-35s %-10s %-15s %-15s %-15s %-15s\n";
Formatter formatter = new Formatter(resultsBuilder, Locale.US);
formatter.format(format, "Data Structure ("+ARRAY_SIZE+" items)", "Add time", "Remove time", "Lookup time", "Query", "Size");
double KB = 1000;
double MB = 1000 * KB;
double MILLIS = 1000000;
double SECOND = 1000;
double MINUTES = 60 * SECOND;
for (int i=0; i<TESTS; i++) {
String name = names[i];
long[] result = results[i];
if (name != null && result != null) {
double size = result[0];
size /= number;
String sizeString = null;
if (size > MB) {
size = size / MB;
sizeString = FORMAT.format(size) + " MB";
} else if (size > KB) {
size = size / KB;
sizeString = FORMAT.format(size) + " KB";
} else {
sizeString = FORMAT.format(size) + " Bytes";
}
double addTime = result[1] / MILLIS;
addTime /= number;
String addTimeString = null;
if (addTime > MINUTES) {
addTime /= MINUTES;
addTimeString = FORMAT.format(addTime) + " m";
} else if (addTime > SECOND) {
addTime /= SECOND;
addTimeString = FORMAT.format(addTime) + " s";
} else {
addTimeString = FORMAT.format(addTime) + " ms";
}
double lookupTime = result[2] / MILLIS;
lookupTime /= number;
String lookupTimeString = null;
if (lookupTime > MINUTES) {
lookupTime /= MINUTES;
lookupTimeString = FORMAT.format(lookupTime) + " m";
} else if (lookupTime > SECOND) {
lookupTime /= SECOND;
lookupTimeString = FORMAT.format(lookupTime) + " s";
} else {
lookupTimeString = FORMAT.format(lookupTime) + " ms";
}
double addQueryTime = result[3] / MILLIS;
addQueryTime /= number;
String queryTimeString = null;
if (addQueryTime > MINUTES) {
addQueryTime /= MINUTES;
queryTimeString = FORMAT.format(addQueryTime) + " m";
} else if (addQueryTime > SECOND) {
addQueryTime /= SECOND;
queryTimeString = FORMAT.format(addQueryTime) + " s";
} else {
queryTimeString = FORMAT.format(addQueryTime) + " ms";
}
double removeTime = result[4] / MILLIS;
removeTime /= number;
String removeTimeString = null;
if (removeTime > MINUTES) {
removeTime /= MINUTES;
removeTimeString = FORMAT.format(removeTime) + " m";
} else if (removeTime > SECOND) {
removeTime /= SECOND;
removeTimeString = FORMAT.format(removeTime) + " s";
} else {
removeTimeString = FORMAT.format(removeTime) + " ms";
}
formatter.format(format, name, addTimeString, removeTimeString, lookupTimeString, queryTimeString, sizeString);
}
}
formatter.close();
return resultsBuilder.toString();
}
// Assertion which won't call toString on the tree unless the assertion fails
private static final <P extends KdTree.XYZPoint> void assertTrue(String msg, P p, KdTree<P> obj, boolean isTrue) {
String toString = "";
if (isTrue==false)
toString = p.toString()+"\n"+"data=["+stringifiedData+"]\n"+obj.toString();
Assert.assertTrue(msg+toString, isTrue);
}
// Assertion which won't call toString on the tree unless the assertion fails
private static final <P extends QuadTree.XYPoint, Q extends QuadTree.XYPoint> void assertTrue(String msg, P p, QuadTree<Q> obj, boolean isTrue) {
String toString = "";
if (isTrue==false)
toString = p.toString()+"\n"+"data=["+stringifiedData+"]\n"+obj.toString();
Assert.assertTrue(msg+toString, isTrue);
}
}