Ticket #13275: cache_Area_intersection_v2.patch
| File cache_Area_intersection_v2.patch, 24.2 KB (added by , 10 years ago) |
|---|
-
src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java
5 5 6 6 import java.awt.Rectangle; 7 7 import java.awt.geom.Area; 8 import java.io.IOException;9 import java.io.ObjectInputStream;10 import java.io.ObjectOutputStream;11 8 import java.util.ArrayList; 12 9 import java.util.Collection; 13 10 import java.util.Collections; 14 11 import java.util.HashSet; 15 12 import java.util.List; 13 import java.util.Objects; 16 14 import java.util.Set; 17 import java.util.concurrent.ForkJoinPool;18 import java.util.concurrent.ForkJoinTask;19 import java.util.concurrent.RecursiveTask;20 15 21 16 import org.openstreetmap.josm.Main; 22 17 import org.openstreetmap.josm.tools.Geometry; … … 23 18 import org.openstreetmap.josm.tools.Geometry.PolygonIntersection; 24 19 import org.openstreetmap.josm.tools.MultiMap; 25 20 import org.openstreetmap.josm.tools.Pair; 26 import org.openstreetmap.josm.tools.Utils;27 21 28 22 /** 29 23 * Helper class to build multipolygons from multiple ways. … … 33 27 */ 34 28 public class MultipolygonBuilder { 35 29 36 private static final ForkJoinPool THREAD_POOL = 37 Utils.newForkJoinPool("multipolygon_creation.numberOfThreads", "multipolygon-builder-%d", Thread.NORM_PRIORITY); 30 private static class ResultCache { 31 Geometry.PolygonIntersection[] results; 32 private final int dim; 33 private long countCheck; 34 private long countMiss; 38 35 36 public ResultCache(Collection<JoinedPolygon> polygons) { 37 int id = 0; 38 for (JoinedPolygon p : polygons) 39 p.setCacheId(id++); 40 41 this.dim = id; 42 results = new Geometry.PolygonIntersection[dim*dim]; 43 } 44 45 46 private synchronized PolygonIntersection getCachedResult(JoinedPolygon pa, JoinedPolygon pb){ 47 if (pa.id < 0 || pa.id >= dim ){ 48 throw new JoinedPolygonCreationException(tr("Internal error: unexpected id in 1st polygon", pa.id)); 49 } 50 if (pb.id < 0 || pb.id >= dim ){ 51 throw new JoinedPolygonCreationException(tr("Internal error: unexpected id in 2nd polygon", pb.id)); 52 } 53 countCheck++; 54 int posAB = pa.id * dim + pb.id; 55 if (results[posAB] == null){ 56 countMiss++; 57 PolygonIntersection intersection = Geometry.polygonIntersection(pa.area, pb.area); 58 results[posAB] = intersection; 59 // set the results for exchanged parameters (a is outer b also means b is outer a etc.) 60 int posBA = pb.id * dim + pa.id; 61 if (intersection == PolygonIntersection.OUTSIDE || intersection == PolygonIntersection.CROSSING){ 62 results[posBA] = intersection; 63 } else if (intersection == PolygonIntersection.FIRST_INSIDE_SECOND){ 64 results[posBA] = PolygonIntersection.SECOND_INSIDE_FIRST; 65 } else if (intersection == PolygonIntersection.SECOND_INSIDE_FIRST) 66 results[posBA] = PolygonIntersection.FIRST_INSIDE_SECOND; 67 68 } 69 return results[posAB]; 70 } 71 72 @Override 73 public String toString() { 74 return "Tests: " + countCheck + " hit/miss " + (countCheck - countMiss) + "/" + countMiss; 75 } 76 } 77 39 78 /** 40 79 * Represents one polygon that consists of multiple ways. 41 80 */ … … 45 84 public final List<Node> nodes; 46 85 public final Area area; 47 86 public final Rectangle bounds; 87 private int id; 48 88 49 89 /** 50 90 * Constructs a new {@code JoinedPolygon} from given list of ways. … … 57 97 this.nodes = this.getNodes(); 58 98 this.area = Geometry.getArea(nodes); 59 99 this.bounds = area.getBounds(); 100 this.id = -1; 60 101 } 61 102 62 103 /** … … 91 132 92 133 return nodes; 93 134 } 135 136 /** 137 * Set id that is used in ResultCache 138 * @param id 139 */ 140 public void setCacheId(int id) { 141 this.id = id; 142 } 94 143 } 95 144 96 145 /** … … 271 320 return null; 272 321 } 273 322 274 private static Pair<Boolean, List<JoinedPolygon>> findInnerWaysCandidates( JoinedPolygon outerWay, Collection<JoinedPolygon> boundaryWays) {323 private static Pair<Boolean, List<JoinedPolygon>> findInnerWaysCandidates(ResultCache cache, JoinedPolygon outerWay, Collection<JoinedPolygon> boundaryWays) { 275 324 boolean outerGood = true; 276 325 List<JoinedPolygon> innerCandidates = new ArrayList<>(); 277 326 … … 283 332 // Preliminary computation on bounds. If bounds do not intersect, no need to do a costly area intersection 284 333 if (outerWay.bounds.intersects(innerWay.bounds)) { 285 334 // Bounds intersection, let's see in detail 286 PolygonIntersection intersection = Geometry.polygonIntersection(outerWay.area, innerWay.area); 335 PolygonIntersection intersection = cache.getCachedResult(outerWay, innerWay); 336 // PolygonIntersection intersection = Geometry.polygonIntersection(outerWay.area, innerWay.area); 287 337 288 338 if (intersection == PolygonIntersection.FIRST_INSIDE_SECOND) { 289 339 outerGood = false; // outer is inside another polygon … … 306 356 * @return the outermostWay, or {@code null} if intersection found. 307 357 */ 308 358 private static List<PolygonLevel> findOuterWaysMultiThread(List<JoinedPolygon> boundaryWays) { 309 return THREAD_POOL.invoke(new Worker(boundaryWays, 0, boundaryWays.size(), new ArrayList<PolygonLevel>(), 310 Math.max(32, boundaryWays.size() / THREAD_POOL.getParallelism() / 3))); 359 ResultCache cache = new ResultCache(boundaryWays); 360 final List<PolygonLevel> output = new ArrayList<>(); 361 List<PolygonLevel> res = boundaryWays.parallelStream() 362 .map(way -> Worker.processOuterWay(0, cache, boundaryWays, output, way)) 363 .allMatch(Objects::nonNull) ? output : null; 364 if (!boundaryWays.isEmpty()) 365 Main.debug("mp cache: " + cache.toString()); 366 return res; 311 367 } 312 368 313 private static class Worker extends RecursiveTask<List<PolygonLevel>>{369 private static class Worker { 314 370 315 // Needed for Findbugs / Coverity because parent class is serializable316 private static final long serialVersionUID = 1L;317 318 private final transient List<JoinedPolygon> input;319 private final int from;320 private final int to;321 private final transient List<PolygonLevel> output;322 private final int directExecutionTaskSize;323 324 Worker(List<JoinedPolygon> input, int from, int to, List<PolygonLevel> output, int directExecutionTaskSize) {325 this.input = input;326 this.from = from;327 this.to = to;328 this.output = output;329 this.directExecutionTaskSize = directExecutionTaskSize;330 }331 332 371 /** 333 372 * Collects outer way and corresponding inner ways from all boundaries. 334 373 * @param level nesting level 374 * @param cache cache that tracks previously calculated results 335 375 * @param boundaryWays boundary ways 336 376 * @return the outermostWay, or {@code null} if intersection found. 337 377 */ 338 private static List<PolygonLevel> findOuterWaysRecursive(int level, List<JoinedPolygon> boundaryWays) {378 private static List<PolygonLevel> findOuterWaysRecursive(int level, ResultCache cache, List<JoinedPolygon> boundaryWays) { 339 379 340 380 final List<PolygonLevel> result = new ArrayList<>(); 341 381 342 382 for (JoinedPolygon outerWay : boundaryWays) { 343 if (processOuterWay(level, boundaryWays, result, outerWay) == null) {383 if (processOuterWay(level, cache, boundaryWays, result, outerWay) == null) { 344 384 return null; 345 385 } 346 386 } … … 348 388 return result; 349 389 } 350 390 351 private static List<PolygonLevel> processOuterWay(int level, List<JoinedPolygon> boundaryWays,391 private static List<PolygonLevel> processOuterWay(int level, ResultCache cache, List<JoinedPolygon> boundaryWays, 352 392 final List<PolygonLevel> result, JoinedPolygon outerWay) { 353 Pair<Boolean, List<JoinedPolygon>> p = findInnerWaysCandidates( outerWay, boundaryWays);393 Pair<Boolean, List<JoinedPolygon>> p = findInnerWaysCandidates(cache, outerWay, boundaryWays); 354 394 if (p == null) { 355 395 // ways intersect 356 396 return null; … … 362 402 363 403 //process inner ways 364 404 if (!p.b.isEmpty()) { 365 List<PolygonLevel> innerList = findOuterWaysRecursive(level + 1, p.b);405 List<PolygonLevel> innerList = findOuterWaysRecursive(level + 1, cache, p.b); 366 406 if (innerList == null) { 367 407 return null; //intersection found 368 408 } … … 380 420 } 381 421 return result; 382 422 } 383 384 @Override385 protected List<PolygonLevel> compute() {386 if (to - from <= directExecutionTaskSize) {387 return computeDirectly();388 } else {389 final Collection<ForkJoinTask<List<PolygonLevel>>> tasks = new ArrayList<>();390 for (int fromIndex = from; fromIndex < to; fromIndex += directExecutionTaskSize) {391 tasks.add(new Worker(input, fromIndex, Math.min(fromIndex + directExecutionTaskSize, to),392 new ArrayList<PolygonLevel>(), directExecutionTaskSize));393 }394 for (ForkJoinTask<List<PolygonLevel>> task : ForkJoinTask.invokeAll(tasks)) {395 List<PolygonLevel> res = task.join();396 if (res == null) {397 return null;398 }399 output.addAll(res);400 }401 return output;402 }403 }404 405 List<PolygonLevel> computeDirectly() {406 for (int i = from; i < to; i++) {407 if (processOuterWay(0, input, output, input.get(i)) == null) {408 return null;409 }410 }411 return output;412 }413 414 private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {415 // Needed for Findbugs / Coverity because parent class is serializable416 ois.defaultReadObject();417 }418 419 private void writeObject(ObjectOutputStream oos) throws IOException {420 // Needed for Findbugs / Coverity because parent class is serializable421 oos.defaultWriteObject();422 }423 423 } 424 424 } -
src/org/openstreetmap/josm/data/osm/visitor/paint/RenderBenchmarkCollector.java
2 2 package org.openstreetmap.josm.data.osm.visitor.paint; 3 3 4 4 import java.io.PrintStream; 5 import java.util. List;5 import java.util.Collection; 6 6 import java.util.function.Supplier; 7 7 8 8 import org.openstreetmap.josm.Main; … … 39 39 * @param allStyleElems All the elements that are painted. 40 40 * @return <code>true</code> if the renderer should continue to render 41 41 */ 42 public boolean renderDraw( List<StyleRecord> allStyleElems) {42 public boolean renderDraw(Collection<StyleRecord> allStyleElems) { 43 43 // nop 44 44 return true; 45 45 } … … 74 74 } 75 75 76 76 @Override 77 public boolean renderDraw( List<StyleRecord> allStyleElems) {77 public boolean renderDraw(Collection<StyleRecord> allStyleElems) { 78 78 timeSortingDone = System.currentTimeMillis(); 79 79 return super.renderDraw(allStyleElems); 80 80 } … … 126 126 } 127 127 128 128 @Override 129 public boolean renderDraw( List<StyleRecord> allStyleElems) {129 public boolean renderDraw(Collection<StyleRecord> allStyleElems) { 130 130 boolean res = super.renderDraw(allStyleElems); 131 131 outStream.print("phase 1 (calculate styles): " + Utils.getDurationString(timeSortingDone - timeStart)); 132 132 return res; -
src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
27 27 import java.awt.geom.Rectangle2D; 28 28 import java.util.ArrayList; 29 29 import java.util.Collection; 30 import java.util.Collections;31 30 import java.util.HashMap; 32 31 import java.util.Iterator; 33 32 import java.util.List; 34 33 import java.util.Map; 35 34 import java.util.NoSuchElementException; 35 import java.util.TreeSet; 36 36 import java.util.concurrent.ForkJoinPool; 37 import java.util.concurrent.ForkJoinTask;38 import java.util.concurrent.RecursiveTask;39 37 import java.util.function.Supplier; 38 import java.util.stream.Collectors; 39 import java.util.stream.Stream; 40 40 41 41 import javax.swing.AbstractButton; 42 42 import javax.swing.FocusManager; … … 45 45 import org.openstreetmap.josm.data.Bounds; 46 46 import org.openstreetmap.josm.data.coor.EastNorth; 47 47 import org.openstreetmap.josm.data.osm.BBox; 48 import org.openstreetmap.josm.data.osm.Changeset;49 48 import org.openstreetmap.josm.data.osm.DataSet; 50 49 import org.openstreetmap.josm.data.osm.Node; 51 50 import org.openstreetmap.josm.data.osm.OsmPrimitive; … … 54 53 import org.openstreetmap.josm.data.osm.RelationMember; 55 54 import org.openstreetmap.josm.data.osm.Way; 56 55 import org.openstreetmap.josm.data.osm.WaySegment; 57 import org.openstreetmap.josm.data.osm.visitor.Visitor;58 56 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 59 57 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData; 60 58 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; … … 61 59 import org.openstreetmap.josm.gui.NavigatableComponent; 62 60 import org.openstreetmap.josm.gui.mappaint.ElemStyles; 63 61 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; 64 import org.openstreetmap.josm.gui.mappaint.StyleElementList;65 62 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; 66 63 import org.openstreetmap.josm.gui.mappaint.styleelement.AreaElement; 67 64 import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement; … … 73 70 import org.openstreetmap.josm.gui.mappaint.styleelement.RepeatImageElement.LineImageAlignment; 74 71 import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement; 75 72 import org.openstreetmap.josm.gui.mappaint.styleelement.TextLabel; 76 import org.openstreetmap.josm.tools.CompositeList;77 73 import org.openstreetmap.josm.tools.Geometry; 78 74 import org.openstreetmap.josm.tools.Geometry.AreaAndPerimeter; 79 75 import org.openstreetmap.josm.tools.ImageProvider; 76 import org.openstreetmap.josm.tools.StreamUtils; 80 77 import org.openstreetmap.josm.tools.Utils; 81 78 82 79 /** … … 1779 1776 } 1780 1777 } 1781 1778 1782 private class ComputeStyleListWorker extends RecursiveTask<List<StyleRecord>> implements Visitor { 1783 private final transient List<? extends OsmPrimitive> input; 1784 private final transient List<StyleRecord> output; 1785 1779 private class ComputeStyleListWorker { 1786 1780 private final transient ElemStyles styles = MapPaintStyles.getStyles(); 1787 private final int directExecutionTaskSize;1788 1789 1781 private final boolean drawArea = circum <= Main.pref.getInteger("mappaint.fillareas", 10000000); 1790 1782 private final boolean drawMultipolygon = drawArea && Main.pref.getBoolean("mappaint.multipolygon", true); 1791 1783 private final boolean drawRestriction = Main.pref.getBoolean("mappaint.restriction", true); … … 1792 1784 1793 1785 /** 1794 1786 * Constructs a new {@code ComputeStyleListWorker}. 1795 * @param input the primitives to process1796 * @param output the list of styles to which styles will be added1797 * @param directExecutionTaskSize the threshold deciding whether to subdivide the tasks1798 1787 */ 1799 ComputeStyleListWorker(final List<? extends OsmPrimitive> input, List<StyleRecord> output, int directExecutionTaskSize) { 1800 this.input = input; 1801 this.output = output; 1802 this.directExecutionTaskSize = directExecutionTaskSize; 1788 ComputeStyleListWorker() { 1803 1789 this.styles.setDrawMultipolygon(drawMultipolygon); 1804 1790 } 1805 1791 1806 @Override 1807 protected List<StyleRecord> compute() { 1808 if (input.size() <= directExecutionTaskSize) { 1809 return computeDirectly(); 1810 } else { 1811 final Collection<ForkJoinTask<List<StyleRecord>>> tasks = new ArrayList<>(); 1812 for (int fromIndex = 0; fromIndex < input.size(); fromIndex += directExecutionTaskSize) { 1813 final int toIndex = Math.min(fromIndex + directExecutionTaskSize, input.size()); 1814 final List<StyleRecord> output = new ArrayList<>(directExecutionTaskSize); 1815 tasks.add(new ComputeStyleListWorker(input.subList(fromIndex, toIndex), output, directExecutionTaskSize).fork()); 1816 } 1817 for (ForkJoinTask<List<StyleRecord>> task : tasks) { 1818 output.addAll(task.join()); 1819 } 1820 return output; 1821 } 1792 Stream<StyleRecord> computeNodeStyle(Node osm) { 1793 final int flags = computeFlags(osm, false); 1794 return StreamUtils.toStream(styles.get(osm, circum, nc)) 1795 .map(s -> new StyleRecord(s, osm, flags)); 1822 1796 } 1823 1797 1824 public List<StyleRecord> computeDirectly() { 1825 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().lock(); 1826 try { 1827 for (final OsmPrimitive osm : input) { 1828 if (osm.isDrawable()) { 1829 osm.accept(this); 1830 } 1831 } 1832 return output; 1833 } finally { 1834 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().unlock(); 1835 } 1798 Stream<StyleRecord> computeRelationStyle(Relation osm) { 1799 final int flags = computeFlags(osm, true); 1800 return StreamUtils.toStream(styles.get(osm, circum, nc)) 1801 .filter(s -> 1802 drawMultipolygon && drawArea && s instanceof AreaElement && (flags & FLAG_DISABLED) == 0 1803 || drawRestriction && s instanceof NodeElement 1804 ) 1805 .map(s -> new StyleRecord(s, osm, flags)); 1836 1806 } 1837 1807 1838 @Override 1839 public void visit(Node n) { 1840 add(n, computeFlags(n, false)); 1808 Stream<StyleRecord> computeWayStyle(Way osm) { 1809 final int flags = computeFlags(osm, true); 1810 return StreamUtils.toStream(styles.get(osm, circum, nc)) 1811 .filter(s -> !(!(drawArea && (flags & FLAG_DISABLED) == 0) && s instanceof AreaElement)) 1812 .map(s -> new StyleRecord(s, osm, flags)); 1841 1813 } 1842 1843 @Override1844 public void visit(Way w) {1845 add(w, computeFlags(w, true));1846 }1847 1848 @Override1849 public void visit(Relation r) {1850 add(r, computeFlags(r, true));1851 }1852 1853 @Override1854 public void visit(Changeset cs) {1855 throw new UnsupportedOperationException();1856 }1857 1858 public void add(Node osm, int flags) {1859 StyleElementList sl = styles.get(osm, circum, nc);1860 for (StyleElement s : sl) {1861 output.add(new StyleRecord(s, osm, flags));1862 }1863 }1864 1865 public void add(Relation osm, int flags) {1866 StyleElementList sl = styles.get(osm, circum, nc);1867 for (StyleElement s : sl) {1868 if (drawMultipolygon && drawArea && s instanceof AreaElement && (flags & FLAG_DISABLED) == 0) {1869 output.add(new StyleRecord(s, osm, flags));1870 } else if (drawRestriction && s instanceof NodeElement) {1871 output.add(new StyleRecord(s, osm, flags));1872 }1873 }1874 }1875 1876 public void add(Way osm, int flags) {1877 StyleElementList sl = styles.get(osm, circum, nc);1878 for (StyleElement s : sl) {1879 if (!(drawArea && (flags & FLAG_DISABLED) == 0) && s instanceof AreaElement) {1880 continue;1881 }1882 output.add(new StyleRecord(s, osm, flags));1883 }1884 }1885 1814 } 1886 1815 1887 1816 /** … … 1909 1838 List<Way> ways = data.searchWays(bbox); 1910 1839 List<Relation> relations = data.searchRelations(bbox); 1911 1840 1912 final List<StyleRecord> allStyleElems = new ArrayList<>(nodes.size()+ways.size()+relations.size());1841 final Collection<StyleRecord> allStyleElems; 1913 1842 1914 1843 // Need to process all relations first. 1915 1844 // Reason: Make sure, ElemStyles.getStyleCacheWithRange is … … 1916 1845 // not called for the same primitive in parallel threads. 1917 1846 // (Could be synchronized, but try to avoid this for 1918 1847 // performance reasons.) 1919 THREAD_POOL.invoke(new ComputeStyleListWorker(relations, allStyleElems, 1920 Math.max(20, relations.size() / THREAD_POOL.getParallelism() / 3))); 1921 THREAD_POOL.invoke(new ComputeStyleListWorker(new CompositeList<>(nodes, ways), allStyleElems, 1922 Math.max(100, (nodes.size() + ways.size()) / THREAD_POOL.getParallelism() / 3))); 1848 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().lock(); 1849 try { 1850 final ComputeStyleListWorker worker = new ComputeStyleListWorker(); 1851 // run parallel stream in THREAD_POOL, see https://stackoverflow.com/a/22269778/205629 1852 allStyleElems = THREAD_POOL.submit(() -> 1853 relations.parallelStream().filter(OsmPrimitive::isDrawable).flatMap(worker::computeRelationStyle) 1854 .collect(Collectors.toCollection(TreeSet::new))).get(); 1855 allStyleElems.addAll(THREAD_POOL.submit(() -> Stream.concat( 1856 nodes.parallelStream().filter(OsmPrimitive::isDrawable).flatMap(worker::computeNodeStyle), 1857 ways.parallelStream().filter(OsmPrimitive::isDrawable).flatMap(worker::computeWayStyle) 1858 ).collect(Collectors.toList())).get()); 1859 } catch (Exception ex) { 1860 throw new RuntimeException(ex); 1861 } finally { 1862 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().unlock(); 1863 } 1923 1864 1924 1865 if (!benchmark.renderSort()) { 1925 1866 return; 1926 1867 } 1927 1868 1928 Collections.sort(allStyleElems); // TODO: try parallel sort when switching to Java 81929 1930 1869 if (!benchmark.renderDraw(allStyleElems)) { 1931 1870 return; 1932 1871 } -
test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java
8 8 import java.io.IOException; 9 9 import java.io.InputStream; 10 10 import java.util.ArrayList; 11 import java.util.Collection; 11 12 import java.util.Collections; 12 13 import java.util.EnumMap; 13 14 import java.util.HashMap; … … 331 332 332 333 public static class BenchmarkData extends CapturingBenchmark { 333 334 334 private List<StyleRecord> allStyleElems;335 private Collection<StyleRecord> allStyleElems; 335 336 336 337 @Override 337 public boolean renderDraw( List<StyleRecord> allStyleElems) {338 public boolean renderDraw(Collection<StyleRecord> allStyleElems) { 338 339 this.allStyleElems = allStyleElems; 339 340 return super.renderDraw(allStyleElems); 340 341 }
