Ticket #13275: patch-13275-parallel-draw.patch
| File patch-13275-parallel-draw.patch, 21.6 KB (added by , 10 years ago) |
|---|
-
src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java
diff --git a/src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java b/src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java index 1951a84..030d1e3 100644
a b import static org.openstreetmap.josm.tools.I18n.tr; 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. … … import org.openstreetmap.josm.tools.Utils; 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);38 39 30 /** 40 31 * Represents one polygon that consists of multiple ways. 41 32 */ … … public class MultipolygonBuilder { 306 297 * @return the outermostWay, or {@code null} if intersection found. 307 298 */ 308 299 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))); 300 final List<PolygonLevel> output = new ArrayList<>(); 301 return boundaryWays.parallelStream() 302 .map(way -> Worker.processOuterWay(0, boundaryWays, output, way)) 303 .allMatch(Objects::nonNull) ? output : null; 311 304 } 312 305 313 private static class Worker extends RecursiveTask<List<PolygonLevel>> { 314 315 // Needed for Findbugs / Coverity because parent class is serializable 316 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 } 306 private static class Worker { 331 307 332 308 /** 333 309 * Collects outer way and corresponding inner ways from all boundaries. … … public class MultipolygonBuilder { 380 356 } 381 357 return result; 382 358 } 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 359 } 424 360 } -
src/org/openstreetmap/josm/data/osm/visitor/paint/RenderBenchmarkCollector.java
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/RenderBenchmarkCollector.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/RenderBenchmarkCollector.java index 3acae15..3573347 100644
a b 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; … … public class RenderBenchmarkCollector { 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 } … … public class RenderBenchmarkCollector { 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 } … … public class RenderBenchmarkCollector { 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
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java index 55405c7..12eaaaae 100644
a b import java.awt.geom.GeneralPath; 25 25 import java.awt.geom.Path2D; 26 26 import java.awt.geom.Point2D; 27 27 import java.awt.geom.Rectangle2D; 28 import java.awt.image.BufferedImage; 28 29 import java.util.ArrayList; 29 30 import java.util.Collection; 30 import java.util.Collections;31 31 import java.util.HashMap; 32 32 import java.util.Iterator; 33 33 import java.util.List; 34 34 import java.util.Map; 35 35 import java.util.NoSuchElementException; 36 import java.util.TreeSet; 36 37 import java.util.concurrent.ForkJoinPool; 37 import java.util.concurrent.ForkJoinTask;38 import java.util.concurrent.RecursiveTask;39 38 import java.util.function.Supplier; 39 import java.util.stream.Collectors; 40 import java.util.stream.IntStream; 41 import java.util.stream.Stream; 40 42 41 43 import javax.swing.AbstractButton; 42 44 import javax.swing.FocusManager; … … import org.openstreetmap.josm.Main; 45 47 import org.openstreetmap.josm.data.Bounds; 46 48 import org.openstreetmap.josm.data.coor.EastNorth; 47 49 import org.openstreetmap.josm.data.osm.BBox; 48 import org.openstreetmap.josm.data.osm.Changeset;49 50 import org.openstreetmap.josm.data.osm.DataSet; 50 51 import org.openstreetmap.josm.data.osm.Node; 51 52 import org.openstreetmap.josm.data.osm.OsmPrimitive; … … import org.openstreetmap.josm.data.osm.Relation; 54 55 import org.openstreetmap.josm.data.osm.RelationMember; 55 56 import org.openstreetmap.josm.data.osm.Way; 56 57 import org.openstreetmap.josm.data.osm.WaySegment; 57 import org.openstreetmap.josm.data.osm.visitor.Visitor;58 58 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 59 59 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData; 60 60 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 61 61 import org.openstreetmap.josm.gui.NavigatableComponent; 62 62 import org.openstreetmap.josm.gui.mappaint.ElemStyles; 63 63 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; 64 import org.openstreetmap.josm.gui.mappaint.StyleElementList;65 64 import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; 66 65 import org.openstreetmap.josm.gui.mappaint.styleelement.AreaElement; 67 66 import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement; … … import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement.Symbol; 73 72 import org.openstreetmap.josm.gui.mappaint.styleelement.RepeatImageElement.LineImageAlignment; 74 73 import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement; 75 74 import org.openstreetmap.josm.gui.mappaint.styleelement.TextLabel; 76 import org.openstreetmap.josm.tools.CompositeList;77 75 import org.openstreetmap.josm.tools.Geometry; 78 76 import org.openstreetmap.josm.tools.Geometry.AreaAndPerimeter; 79 77 import org.openstreetmap.josm.tools.ImageProvider; 78 import org.openstreetmap.josm.tools.Pair; 79 import org.openstreetmap.josm.tools.StreamUtils; 80 80 import org.openstreetmap.josm.tools.Utils; 81 81 82 82 /** … … public class StyledMapRenderer extends AbstractMapRenderer { 1779 1779 } 1780 1780 } 1781 1781 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 1782 private class ComputeStyleListWorker { 1786 1783 private final transient ElemStyles styles = MapPaintStyles.getStyles(); 1787 private final int directExecutionTaskSize;1788 1789 1784 private final boolean drawArea = circum <= Main.pref.getInteger("mappaint.fillareas", 10000000); 1790 1785 private final boolean drawMultipolygon = drawArea && Main.pref.getBoolean("mappaint.multipolygon", true); 1791 1786 private final boolean drawRestriction = Main.pref.getBoolean("mappaint.restriction", true); 1792 1787 1793 1788 /** 1794 1789 * 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 1790 */ 1799 ComputeStyleListWorker(final List<? extends OsmPrimitive> input, List<StyleRecord> output, int directExecutionTaskSize) { 1800 this.input = input; 1801 this.output = output; 1802 this.directExecutionTaskSize = directExecutionTaskSize; 1791 ComputeStyleListWorker() { 1803 1792 this.styles.setDrawMultipolygon(drawMultipolygon); 1804 1793 } 1805 1794 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 } 1822 } 1823 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 } 1836 } 1837 1838 @Override 1839 public void visit(Node n) { 1840 add(n, computeFlags(n, false)); 1841 } 1842 1843 @Override 1844 public void visit(Way w) { 1845 add(w, computeFlags(w, true)); 1846 } 1847 1848 @Override 1849 public void visit(Relation r) { 1850 add(r, computeFlags(r, true)); 1851 } 1852 1853 @Override 1854 public void visit(Changeset cs) { 1855 throw new UnsupportedOperationException(); 1795 Stream<StyleRecord> computeNodeStyle(Node osm) { 1796 final int flags = computeFlags(osm, false); 1797 return StreamUtils.toStream(styles.get(osm, circum, nc)) 1798 .map(s -> new StyleRecord(s, osm, flags)); 1856 1799 } 1857 1800 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 } 1801 Stream<StyleRecord> computeRelationStyle(Relation osm) { 1802 final int flags = computeFlags(osm, true); 1803 return StreamUtils.toStream(styles.get(osm, circum, nc)) 1804 .filter(s -> 1805 drawMultipolygon && drawArea && s instanceof AreaElement && (flags & FLAG_DISABLED) == 0 1806 || drawRestriction && s instanceof NodeElement 1807 ) 1808 .map(s -> new StyleRecord(s, osm, flags)); 1863 1809 } 1864 1810 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 } 1811 Stream<StyleRecord> computeWayStyle(Way osm) { 1812 final int flags = computeFlags(osm, true); 1813 return StreamUtils.toStream(styles.get(osm, circum, nc)) 1814 .filter(s -> !(!(drawArea && (flags & FLAG_DISABLED) == 0) && s instanceof AreaElement)) 1815 .map(s -> new StyleRecord(s, osm, flags)); 1884 1816 } 1885 1817 } 1886 1818 … … public class StyledMapRenderer extends AbstractMapRenderer { 1909 1841 List<Way> ways = data.searchWays(bbox); 1910 1842 List<Relation> relations = data.searchRelations(bbox); 1911 1843 1912 final List<StyleRecord> allStyleElems = new ArrayList<>(nodes.size()+ways.size()+relations.size());1844 final Collection<StyleRecord> allStyleElems; 1913 1845 1914 1846 // Need to process all relations first. 1915 1847 // Reason: Make sure, ElemStyles.getStyleCacheWithRange is 1916 1848 // not called for the same primitive in parallel threads. 1917 1849 // (Could be synchronized, but try to avoid this for 1918 1850 // 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))); 1851 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().lock(); 1852 try { 1853 final ComputeStyleListWorker worker = new ComputeStyleListWorker(); 1854 // run parallel stream in THREAD_POOL, see https://stackoverflow.com/a/22269778/205629 1855 allStyleElems = THREAD_POOL.submit(() -> 1856 relations.parallelStream().filter(OsmPrimitive::isDrawable).flatMap(worker::computeRelationStyle) 1857 .collect(Collectors.toCollection(TreeSet::new))).get(); 1858 allStyleElems.addAll(THREAD_POOL.submit(() -> Stream.concat( 1859 nodes.parallelStream().filter(OsmPrimitive::isDrawable).flatMap(worker::computeNodeStyle), 1860 ways.parallelStream().filter(OsmPrimitive::isDrawable).flatMap(worker::computeWayStyle) 1861 ).collect(Collectors.toList())).get()); 1862 } catch (Exception ex) { 1863 throw new RuntimeException(ex); 1864 } finally { 1865 MapCSSStyleSource.STYLE_SOURCE_LOCK.readLock().unlock(); 1866 } 1923 1867 1924 1868 if (!benchmark.renderSort()) { 1925 1869 return; 1926 1870 } 1927 1871 1928 Collections.sort(allStyleElems); // TODO: try parallel sort when switching to Java 81929 1930 1872 if (!benchmark.renderDraw(allStyleElems)) { 1931 1873 return; 1932 1874 } 1933 1934 for (StyleRecord r : allStyleElems) { 1935 r.style.paintPrimitive( 1936 r.osm, 1937 paintSettings, 1938 this, 1939 (r.flags & FLAG_SELECTED) != 0, 1940 (r.flags & FLAG_OUTERMEMBER_OF_SELECTED) != 0, 1941 (r.flags & FLAG_MEMBER_OF_SELECTED) != 0 1942 ); 1943 } 1875 ArrayList<StyleRecord> list = new ArrayList<>(allStyleElems); 1876 int SPLIT = 8; 1877 IntStream.range(0, SPLIT).parallel() 1878 .mapToObj(i -> new Pair<>(getSplitIndex(list, SPLIT, i), getSplitIndex(list, SPLIT, i + 1))) 1879 .map(range -> { 1880 // long start = System.currentTimeMillis(); 1881 Pair<BufferedImage, StyledMapRenderer> render = range.a == 0 1882 ? new Pair<>(null, this) 1883 : newRenderer(renderVirtualNodes); 1884 for (StyleRecord r : list.subList(range.a, range.b)) { 1885 r.style.paintPrimitive( 1886 r.osm, 1887 paintSettings, 1888 render.b, 1889 (r.flags & FLAG_SELECTED) != 0, 1890 (r.flags & FLAG_OUTERMEMBER_OF_SELECTED) != 0, 1891 (r.flags & FLAG_MEMBER_OF_SELECTED) != 0 1892 ); 1893 } 1894 // System.out.println("Sub for " + range.a + ": " + (System.currentTimeMillis() - start)); 1895 return render; 1896 }).forEachOrdered(res -> {if (res.a != null) {g.drawImage(res.a, 0, 0, null);}}); 1944 1897 1945 1898 drawVirtualNodes(data, bbox); 1946 1899 … … public class StyledMapRenderer extends AbstractMapRenderer { 1949 1902 data.getReadLock().unlock(); 1950 1903 } 1951 1904 } 1905 1906 private int getSplitIndex(ArrayList<StyleRecord> list, int SPLIT, int i) { 1907 int size = list.size(); 1908 double part = (double) i / SPLIT; 1909 part = .8 * part * part + .2 * part; // seems to work fine 1910 return Math.min(size, (int) (size * part)); 1911 } 1912 1913 private Pair<BufferedImage, StyledMapRenderer> newRenderer(boolean renderVirtualNodes) { 1914 BufferedImage backing = new BufferedImage(g.getClipBounds().width, g.getClipBounds().height, BufferedImage.TYPE_4BYTE_ABGR); 1915 StyledMapRenderer b = new StyledMapRenderer((Graphics2D) backing.getGraphics(), nc, isOutlineOnly); 1916 b.getSettings(renderVirtualNodes); 1917 return new Pair<>(backing, b); 1918 } 1952 1919 } -
test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java
diff --git a/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java b/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java index 8e5d8d5..aeae2cf 100644
a b import java.io.File; 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; … … public class MapRendererPerformanceTest { 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 }
