/* * IntervalTreeSpeedTest.java * * Copyright (C) 2014 Leo Osvald <leo.osvald@gmail.com> * * This file is part of SGLJ. * * SGLJ is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SGLJ is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see <http://www.gnu.org/licenses/>. */ package org.sglj.util.struct; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.TreeSet; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import org.sglj.util.struct.AvlTree.AvlNode; import org.sglj.util.struct.IntervalTree.IntervalTraits; import org.sglj.util.struct.IntervalTree.Node; import org.sglj.util.struct.IntervalTreeTest.Interval; @RunWith(Suite.class) @SuiteClasses({ IntervalTreeSpeedTest.Find10mDisjointPointsITTest.class, IntervalTreeSpeedTest.Find10mDisjointPointsABFTest.class, IntervalTreeSpeedTest.Find1mDisjointPointsITTest.class, IntervalTreeSpeedTest.Find1mDisjointPointsABFTest.class, IntervalTreeSpeedTest.Find10mDisjointIntervalsITTest.class, IntervalTreeSpeedTest.Find10mDisjointIntervalsABFTest.class, IntervalTreeSpeedTest.Find1mDisjointIntervalsITTest.class, IntervalTreeSpeedTest.Find1mDisjointIntervalsABFTest.class, IntervalTreeSpeedTest.Find1mConstant2OverlappingITTest.class, IntervalTreeSpeedTest.Find1mConstant2OverlappingABFTest.class, IntervalTreeSpeedTest.Find1mConstant4OverlappingITTest.class, IntervalTreeSpeedTest.Find1mConstant4OverlappingABFTest.class, IntervalTreeSpeedTest.Find1mConstant8OverlappingITTest.class, IntervalTreeSpeedTest.Find1mConstant8OverlappingABFTest.class, IntervalTreeSpeedTest.Find100kConstant100OverlappingITTest.class, IntervalTreeSpeedTest.Find100kConstant100OverlappingABFTest.class, IntervalTreeSpeedTest.Find1mLogOverlappingITTest.class, IntervalTreeSpeedTest.Find1mLogOverlappingABFTest.class, }) public class IntervalTreeSpeedTest { static final IntervalTraits<Interval, Integer> traits = new IntervalTraits<Interval, Integer>() { @Override public Integer from(Interval interval) { return interval.from; } @Override public Integer to(Interval interval) { return interval.to; } @Override public Interval pointInterval(Integer endpoint) { return new Interval(endpoint, endpoint, Integer.MIN_VALUE); } }; static IntervalTree<Interval, Integer> create() { return new IntervalTree<Interval, Integer>(traits); } static abstract class Finder { int hash; List<Interval> result = new ArrayList<Interval>(); //{ // public boolean add(Interval e) { // return false; // } // public boolean addAll(java.util.Collection<? extends Interval> c) { // for (Interval in : c) { // } // return false; // } // }; ArrayList<Integer> overlappingPoints = new ArrayList<Integer>(); ArrayList<Integer> nonOverlappingPoints = new ArrayList<Integer>(); void find(Integer point) { result.clear(); findOverlapping(point, result); hash += result.hashCode(); } abstract void findOverlapping(Integer point, Collection<Interval> result); abstract void add(Interval in); abstract void remove(Interval in); abstract void clear(); abstract String repr(); void setDisjointPoints(int n, int coordMax, Random random) { if (coordMax < n) throw new IllegalArgumentException(); clear(); HashSet<Integer> hs = new HashSet<Integer>(n); do { Integer e = random.nextInt(coordMax); if (!hs.add(e)) continue; overlappingPoints.add(e); add(new Interval(e, e, -2)); } while (hs.size() < n); do { Integer e = random.nextInt(coordMax); if (hs.contains(e)) continue; nonOverlappingPoints.add(e); hs.add(e); } while (nonOverlappingPoints.size() != overlappingPoints.size()); } private void initNonOverlappingPoints(int n, int coordMax, Random random) { HashSet<Integer> hs = new HashSet<Integer>(); nonOverlappingPoints.clear(); do { Integer e = -random.nextInt(coordMax - 3) - 1; if (hs.contains(e)) continue; nonOverlappingPoints.add(e); hs.add(e); } while (nonOverlappingPoints.size() != n); } void setLinearOverlapping(int n, int coordMax, Random random) { clear(); Interval[] intervals = new Interval[n]; for (int i = 0; i < n; ++i) { add(intervals[i] = IntervalTreeTest.randomInterval(random, coordMax)); } HashSet<Integer> hs = new HashSet<Integer>(n); overlappingPoints.clear(); do { Integer i = random.nextInt(n); int from = intervals[i].from; int to = intervals[i].to; int e = from + random.nextInt(to - from + 1); if (!hs.add(e)) continue; overlappingPoints.add(e); } while (hs.size() < n); initNonOverlappingPoints(n, coordMax, random); } void setConstantOverlapping(int n, int k, int coordMax, Random random) { if (k < 1 || n < k) throw new IllegalArgumentException(); clear(); overlappingPoints.clear(); List<TreeSet<Integer>> froms = new ArrayList<TreeSet<Integer>>(k); for (int i = 0; i < k; ++i) { froms.add(new TreeSet<Integer>()); froms.get(froms.size() - 1).add(0); overlappingPoints.add(1 + random.nextInt(coordMax)); } initNonOverlappingPoints(n, coordMax, random); n -= k; while (n != 0) { if (!froms.get(random.nextInt(k)).add( 1 + random.nextInt(coordMax))) continue; --n; } for (int i = 0; i < k; ++i) { Integer to = coordMax; for (Integer from : froms.get(i).descendingSet()) { add(new Interval(from, to)); to = from - 1; } } } void findOverlapping(int n, Random random) { findOverlapping(overlappingPoints, n, random); } void findNonOverlapping(int n, Random random) { findOverlapping(nonOverlappingPoints, n, random); } private void findOverlapping(ArrayList<Integer> points, int n, Random random) { while (n-- != 0) find(points.get(random.nextInt(points.size()))); } @Override public String toString() { return "{>}: " + overlappingPoints + "\n" + repr(); } } static abstract class BruteForceFinder extends Finder { Collection<Interval> intervals = init(); abstract Collection<Interval> init(); public void findOverlapping(Integer point, Collection<Interval> result) { for (Interval interval : intervals) { if (interval.from.compareTo(point) <= 0 && point.compareTo(interval.to) <= 0) result.add(interval); } } @Override void add(Interval in) { intervals.add(in); } @Override void remove(Interval in) { intervals.remove(in); } @Override void clear() { intervals.clear(); } @Override String repr() { return intervals.toString(); } } static class IntervalTreeFinder extends Finder { IntervalTree<Interval, Integer> t = create(); @Override void findOverlapping(Integer point, Collection<Interval> result) { t.findOverlapping(point, result); } @Override void add(Interval in) { t.add(in); } @Override void remove(Interval in) { t.remove(in); } @Override void clear() { t.clear(); } private static String repr(AvlNode<?> node) { @SuppressWarnings("unchecked") Node<Interval, Integer> n = (Node<Interval, Integer>)node; return n.point.from + ": " + n.asc; } static void repr(AvlNode<?> node, StringBuilder sb) { sb.append('('); if (node != null) { sb.append(repr(node)); sb.append(" <"); repr(node.left, sb); sb.append(" >"); repr(node.right, sb); } sb.append(')'); } @Override String repr() { StringBuilder sb = new StringBuilder(); repr(t.getRoot(), sb); return sb.toString(); } } static class ArrayBruteForceFinder extends BruteForceFinder { @Override Collection<Interval> init() { return new ArrayList<Interval>(); } } public static abstract class TestBase { static final int TIMEOUT = 2000; private Method testMethod; private int attempts; abstract int getN(); Finder createFinder() { return new IntervalTreeFinder(); } @Before public void resetIntervalId() { Interval.curId = 0; } protected void profileLater(int attempts) { if (!isProfilingEnabled()) return; try { testMethod = this.getClass().getMethod( Thread.currentThread().getStackTrace()[3] .getMethodName()); Test annot = testMethod.getAnnotation(Test.class); if (annot == null || skipProfiling(annot)) testMethod = null; else { this.attempts = attempts; } } catch (Exception e) { } } protected boolean skipProfiling(Test test) { return test.timeout() != 0 && !this.getClass().getName().endsWith("ITTest"); } @After public void rerunProfiled() { if (!isProfilingEnabled() || testMethod == null) return; try { long minTime = Long.MAX_VALUE; for (int attempts = this.attempts; attempts-- > 0; ) { long time = System.nanoTime(); testMethod.invoke(this); minTime = Math.min(System.nanoTime() - time, minTime); } System.err.printf("%s.%s\t%4.6f\n", this.getClass().getCanonicalName(), testMethod.getName(), (double)minTime / 1e6); } catch (Exception e) { Assert.fail("Failed to repeat test: " + testMethod); } finally { testMethod = null; } } protected boolean isProfilingEnabled() { return true; } } static void testDisjointPoints(int findCount, int size, int coordMax, Random random, Finder f) { f.setDisjointPoints(size, coordMax, random); f.findOverlapping(findCount, random); } static abstract class FindDisjointPointsTestBase extends TestBase { void test(int size, int attempts) { profileLater(attempts); testDisjointPoints(getN(), size, Integer.MAX_VALUE, new Random(42), createFinder()); } } public static class Find10mDisjointPointsITTest extends FindDisjointPointsTestBase { @Override int getN() { return 10000000; } @Test public void test01() { test(1, 1); } @Test public void test02() { test(2, 1); } @Test public void test04() { test(4, 1); } @Test public void test08() { test(8, 1); } @Test public void test16() { test(16, 1); } } public static class Find10mDisjointPointsABFTest extends Find10mDisjointPointsITTest { @Override Finder createFinder() { return new ArrayBruteForceFinder(); } } public static class Find1mDisjointPointsITTest extends FindDisjointPointsTestBase { static final int TIMEOUT = 3300; @Override int getN() { return 1000000; } @Test public void test000025() { test(25, 2); } @Test public void test000050() { test(50, 1); } @Test public void test000100() { test(100, 1); } @Test public void test000200() { test(200, 1); } @Test public void test000400() { test(400, 1); } @Test(timeout=TIMEOUT/2) public void test001000() { test(1000, 2); } @Test(timeout=TIMEOUT/2) public void test002000() { test(2000, 2); } @Test(timeout=TIMEOUT/2) public void test004000() { test(4000, 2); } @Test(timeout=TIMEOUT/2) public void test010000() { test(10000, 1); } @Test(timeout=TIMEOUT) public void test020000() { test(20000, 2); } @Test(timeout=TIMEOUT) public void test040000() { test(40000, 2); } @Test(timeout=TIMEOUT) public void test100000() { test(100000, 2); } } public static class Find1mDisjointPointsABFTest extends Find1mDisjointPointsITTest { @Override Finder createFinder() { return new ArrayBruteForceFinder(); } } static void testConstantOverlap(int findCount, int size, int k, int coordMax, Random random, Finder f) { f.setConstantOverlapping(size, k, coordMax, random); f.findNonOverlapping(findCount, random); //System.out.println(f); } public static class Find10mDisjointIntervalsITTest extends Find10mDisjointPointsITTest { void test(int size, int attempts) { profileLater(attempts); testConstantOverlap(getN(), size, 1, Integer.MAX_VALUE, new Random(42), createFinder()); } } public static class Find10mDisjointIntervalsABFTest extends Find10mDisjointIntervalsITTest { @Override Finder createFinder() { return new ArrayBruteForceFinder(); } } public static class Find1mDisjointIntervalsITTest extends Find1mDisjointPointsITTest { void test(int size, int attempts) { profileLater(attempts); testConstantOverlap(getN(), size, 1, Integer.MAX_VALUE, new Random(42), createFinder()); } } public static class Find1mDisjointIntervalsABFTest extends Find1mDisjointIntervalsITTest { @Override Finder createFinder() { return new ArrayBruteForceFinder(); } } static abstract class FindConstantOverlappingTestBase extends TestBase { abstract int getK(); void test(int size) { profileLater(1); testConstantOverlap(getN(), size, getK(), Integer.MAX_VALUE, new Random(42), createFinder()); } } public static class Find1mConstant2OverlappingITTest extends FindConstantOverlappingTestBase { static final int TIMEOUT = 4500; @Override int getN() { return 1000000; } @Override int getK() { return 2; } @Test public void test000002() { test(2); } @Test public void test000003() { test(3); } @Test public void test000004() { test(4); } @Test public void test000005() { test(5); } @Test public void test000008() { test(8); } @Test public void test000010() { test(10); } @Test public void test000016() { test(16); } @Test public void test000020() { test(20); } @Test public void test000040() { test(40); } @Test public void test000080() { test(80); } @Test public void test000100() { test(100); } @Test public void test000200() { test(200); } @Test public void test000320() { test(320); } @Test public void test000400() { test(400); } @Test(timeout=TIMEOUT/2) public void test001000() { test(1000); } @Test(timeout=TIMEOUT/2) public void test002000() { test(2000); } @Test(timeout=TIMEOUT/2) public void test004000() { test(4000); } @Test(timeout=TIMEOUT) public void test010000() { test(10000); } @Test(timeout=TIMEOUT) public void test020000() { test(20000); } @Test(timeout=TIMEOUT) public void test040000() { test(40000); } @Test(timeout=TIMEOUT) public void test100000() { test(100000); } } public static class Find1mConstant2OverlappingABFTest extends Find1mConstant2OverlappingITTest { @Override Finder createFinder() { return new ArrayBruteForceFinder(); } } public static class Find1mConstant4OverlappingITTest extends Find1mConstant2OverlappingITTest { @Override int getK() { return 4; } } public static class Find1mConstant4OverlappingABFTest extends Find1mConstant2OverlappingABFTest { @Override int getK() { return 4; } } public static class Find1mConstant8OverlappingITTest extends Find1mConstant4OverlappingITTest { @Override int getK() { return 8; } } public static class Find1mConstant8OverlappingABFTest extends Find1mConstant4OverlappingABFTest { @Override int getK() { return 8; } } public static class Find100kConstant100OverlappingITTest extends FindConstantOverlappingTestBase { static final int TIMEOUT = 2500; @Override int getN() { return 100000; } @Override int getK() { return 100; } @Test public void test000100() { test(100); } @Test public void test000200() { test(200); } @Test public void test000400() { test(400); } @Test public void test001000() { test(1000); } @Test public void test002000() { test(2000); } @Test public void test004000() { test(4000); } @Test public void test010000() { test(10000); } @Test public void test020000() { test(20000); } @Test public void test040000() { test(40000); } @Test public void test100000() { test(100000); } } public static class Find100kConstant100OverlappingABFTest extends Find100kConstant100OverlappingITTest { @Override Finder createFinder() { return new ArrayBruteForceFinder(); } } static abstract class FindLogOverlappingTestBase extends TestBase { void test(int size) { profileLater(1); testConstantOverlap(getN(), size, 32 - Integer.numberOfLeadingZeros(size), Integer.MAX_VALUE, new Random(42), createFinder()); } } public static class Find1mLogOverlappingITTest extends FindLogOverlappingTestBase { static final int TIMEOUT = 6000; @Override int getN() { return 1000000; } @Test public void test000004() { test(4); } @Test public void test000010() { test(10); } @Test public void test000020() { test(20); } @Test public void test000040() { test(40); } @Test public void test000100() { test(100); } @Test public void test000200() { test(200); } @Test public void test000400() { test(400); } @Test(timeout=TIMEOUT/2) public void test001000() { test(1000); } @Test(timeout=TIMEOUT/2) public void test002000() { test(2000); } @Test(timeout=TIMEOUT/2) public void test004000() { test(4000); } @Test(timeout=TIMEOUT/2) public void test010000() { test(10000); } @Test(timeout=TIMEOUT) public void test020000() { test(20000); } @Test(timeout=TIMEOUT) public void test040000() { test(40000); } @Test(timeout=TIMEOUT) public void test100000() { test(100000); } @Test(timeout=TIMEOUT) public void test200000() { test(200000); } @Test(timeout=2*TIMEOUT) public void test400000() { test(400000); } } public static class Find1mLogOverlappingABFTest extends Find1mLogOverlappingITTest { @Override Finder createFinder() { return new ArrayBruteForceFinder(); } } static void testLinearOverlap(int findCount, int size, int coordMax, Random random, Finder f) { f.setLinearOverlapping(size, coordMax, random); f.findOverlapping(findCount, random); //System.out.println(f); } static abstract class FindLinearOverlappingTestBase extends TestBase { void test(int size) { profileLater(1); testLinearOverlap(getN(), size, Integer.MAX_VALUE, new Random(42), createFinder()); } } public static class Find10kLinearOverlappingITTest extends FindLinearOverlappingTestBase { @Override int getN() { return 10000; } @Test(timeout=TIMEOUT) public void test1() { test(1); } @Test(timeout=TIMEOUT) public void test10() { test(10); } @Test(timeout=TIMEOUT) public void test100() { test(100); } } public static class Find10kLinearOverlappingABFTest extends Find10kLinearOverlappingITTest { @Override Finder createFinder() { return new ArrayBruteForceFinder(); } } } // Profile output beautify command: // sed -r -e 's/k('$'\t'')/000\1/' -e 's/.test/'$'\t''/' -e 's/.*SpeedTest\.//'