Ticket #13275: patch-13275-parallel-draw.patch

File patch-13275-parallel-draw.patch, 21.6 KB (added by michael2402, 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;  
    55
    66import java.awt.Rectangle;
    77import java.awt.geom.Area;
    8 import java.io.IOException;
    9 import java.io.ObjectInputStream;
    10 import java.io.ObjectOutputStream;
    118import java.util.ArrayList;
    129import java.util.Collection;
    1310import java.util.Collections;
    1411import java.util.HashSet;
    1512import java.util.List;
     13import java.util.Objects;
    1614import java.util.Set;
    17 import java.util.concurrent.ForkJoinPool;
    18 import java.util.concurrent.ForkJoinTask;
    19 import java.util.concurrent.RecursiveTask;
    2015
    2116import org.openstreetmap.josm.Main;
    2217import org.openstreetmap.josm.tools.Geometry;
    2318import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
    2419import org.openstreetmap.josm.tools.MultiMap;
    2520import org.openstreetmap.josm.tools.Pair;
    26 import org.openstreetmap.josm.tools.Utils;
    2721
    2822/**
    2923 * Helper class to build multipolygons from multiple ways.
    import org.openstreetmap.josm.tools.Utils;  
    3327 */
    3428public class MultipolygonBuilder {
    3529
    36     private static final ForkJoinPool THREAD_POOL =
    37             Utils.newForkJoinPool("multipolygon_creation.numberOfThreads", "multipolygon-builder-%d", Thread.NORM_PRIORITY);
    38 
    3930    /**
    4031     * Represents one polygon that consists of multiple ways.
    4132     */
    public class MultipolygonBuilder {  
    306297     * @return the outermostWay, or {@code null} if intersection found.
    307298     */
    308299    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;
    311304    }
    312305
    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 {
    331307
    332308        /**
    333309         * Collects outer way and corresponding inner ways from all boundaries.
    public class MultipolygonBuilder {  
    380356            }
    381357            return result;
    382358        }
    383 
    384         @Override
    385         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 serializable
    416             ois.defaultReadObject();
    417         }
    418 
    419         private void writeObject(ObjectOutputStream oos) throws IOException {
    420             // Needed for Findbugs / Coverity because parent class is serializable
    421             oos.defaultWriteObject();
    422         }
    423359    }
    424360}
  • 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  
    22package org.openstreetmap.josm.data.osm.visitor.paint;
    33
    44import java.io.PrintStream;
    5 import java.util.List;
     5import java.util.Collection;
    66import java.util.function.Supplier;
    77
    88import org.openstreetmap.josm.Main;
    public class RenderBenchmarkCollector {  
    3939     * @param allStyleElems All the elements that are painted.
    4040     * @return <code>true</code> if the renderer should continue to render
    4141     */
    42     public boolean renderDraw(List<StyleRecord> allStyleElems) {
     42    public boolean renderDraw(Collection<StyleRecord> allStyleElems) {
    4343        // nop
    4444        return true;
    4545    }
    public class RenderBenchmarkCollector {  
    7474        }
    7575
    7676        @Override
    77         public boolean renderDraw(List<StyleRecord> allStyleElems) {
     77        public boolean renderDraw(Collection<StyleRecord> allStyleElems) {
    7878            timeSortingDone = System.currentTimeMillis();
    7979            return super.renderDraw(allStyleElems);
    8080        }
    public class RenderBenchmarkCollector {  
    126126        }
    127127
    128128        @Override
    129         public boolean renderDraw(List<StyleRecord> allStyleElems) {
     129        public boolean renderDraw(Collection<StyleRecord> allStyleElems) {
    130130            boolean res = super.renderDraw(allStyleElems);
    131131            outStream.print("phase 1 (calculate styles): " + Utils.getDurationString(timeSortingDone - timeStart));
    132132            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;  
    2525import java.awt.geom.Path2D;
    2626import java.awt.geom.Point2D;
    2727import java.awt.geom.Rectangle2D;
     28import java.awt.image.BufferedImage;
    2829import java.util.ArrayList;
    2930import java.util.Collection;
    30 import java.util.Collections;
    3131import java.util.HashMap;
    3232import java.util.Iterator;
    3333import java.util.List;
    3434import java.util.Map;
    3535import java.util.NoSuchElementException;
     36import java.util.TreeSet;
    3637import java.util.concurrent.ForkJoinPool;
    37 import java.util.concurrent.ForkJoinTask;
    38 import java.util.concurrent.RecursiveTask;
    3938import java.util.function.Supplier;
     39import java.util.stream.Collectors;
     40import java.util.stream.IntStream;
     41import java.util.stream.Stream;
    4042
    4143import javax.swing.AbstractButton;
    4244import javax.swing.FocusManager;
    import org.openstreetmap.josm.Main;  
    4547import org.openstreetmap.josm.data.Bounds;
    4648import org.openstreetmap.josm.data.coor.EastNorth;
    4749import org.openstreetmap.josm.data.osm.BBox;
    48 import org.openstreetmap.josm.data.osm.Changeset;
    4950import org.openstreetmap.josm.data.osm.DataSet;
    5051import org.openstreetmap.josm.data.osm.Node;
    5152import org.openstreetmap.josm.data.osm.OsmPrimitive;
    import org.openstreetmap.josm.data.osm.Relation;  
    5455import org.openstreetmap.josm.data.osm.RelationMember;
    5556import org.openstreetmap.josm.data.osm.Way;
    5657import org.openstreetmap.josm.data.osm.WaySegment;
    57 import org.openstreetmap.josm.data.osm.visitor.Visitor;
    5858import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
    5959import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
    6060import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
    6161import org.openstreetmap.josm.gui.NavigatableComponent;
    6262import org.openstreetmap.josm.gui.mappaint.ElemStyles;
    6363import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
    64 import org.openstreetmap.josm.gui.mappaint.StyleElementList;
    6564import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
    6665import org.openstreetmap.josm.gui.mappaint.styleelement.AreaElement;
    6766import org.openstreetmap.josm.gui.mappaint.styleelement.BoxTextElement;
    import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement.Symbol;  
    7372import org.openstreetmap.josm.gui.mappaint.styleelement.RepeatImageElement.LineImageAlignment;
    7473import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
    7574import org.openstreetmap.josm.gui.mappaint.styleelement.TextLabel;
    76 import org.openstreetmap.josm.tools.CompositeList;
    7775import org.openstreetmap.josm.tools.Geometry;
    7876import org.openstreetmap.josm.tools.Geometry.AreaAndPerimeter;
    7977import org.openstreetmap.josm.tools.ImageProvider;
     78import org.openstreetmap.josm.tools.Pair;
     79import org.openstreetmap.josm.tools.StreamUtils;
    8080import org.openstreetmap.josm.tools.Utils;
    8181
    8282/**
    public class StyledMapRenderer extends AbstractMapRenderer {  
    17791779        }
    17801780    }
    17811781
    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 {
    17861783        private final transient ElemStyles styles = MapPaintStyles.getStyles();
    1787         private final int directExecutionTaskSize;
    1788 
    17891784        private final boolean drawArea = circum <= Main.pref.getInteger("mappaint.fillareas", 10000000);
    17901785        private final boolean drawMultipolygon = drawArea && Main.pref.getBoolean("mappaint.multipolygon", true);
    17911786        private final boolean drawRestriction = Main.pref.getBoolean("mappaint.restriction", true);
    17921787
    17931788        /**
    17941789         * Constructs a new {@code ComputeStyleListWorker}.
    1795          * @param input the primitives to process
    1796          * @param output the list of styles to which styles will be added
    1797          * @param directExecutionTaskSize the threshold deciding whether to subdivide the tasks
    17981790         */
    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() {
    18031792            this.styles.setDrawMultipolygon(drawMultipolygon);
    18041793        }
    18051794
    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));
    18561799        }
    18571800
    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));
    18631809        }
    18641810
    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));
    18841816        }
    18851817    }
    18861818
    public class StyledMapRenderer extends AbstractMapRenderer {  
    19091841            List<Way> ways = data.searchWays(bbox);
    19101842            List<Relation> relations = data.searchRelations(bbox);
    19111843
    1912             final List<StyleRecord> allStyleElems = new ArrayList<>(nodes.size()+ways.size()+relations.size());
     1844            final Collection<StyleRecord> allStyleElems;
    19131845
    19141846            // Need to process all relations first.
    19151847            // Reason: Make sure, ElemStyles.getStyleCacheWithRange is
    19161848            // not called for the same primitive in parallel threads.
    19171849            // (Could be synchronized, but try to avoid this for
    19181850            // 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            }
    19231867
    19241868            if (!benchmark.renderSort()) {
    19251869                return;
    19261870            }
    19271871
    1928             Collections.sort(allStyleElems); // TODO: try parallel sort when switching to Java 8
    1929 
    19301872            if (!benchmark.renderDraw(allStyleElems)) {
    19311873                return;
    19321874            }
    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);}});
    19441897
    19451898            drawVirtualNodes(data, bbox);
    19461899
    public class StyledMapRenderer extends AbstractMapRenderer {  
    19491902            data.getReadLock().unlock();
    19501903        }
    19511904    }
     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    }
    19521919}
  • 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;  
    88import java.io.IOException;
    99import java.io.InputStream;
    1010import java.util.ArrayList;
     11import java.util.Collection;
    1112import java.util.Collections;
    1213import java.util.EnumMap;
    1314import java.util.HashMap;
    public class MapRendererPerformanceTest {  
    331332
    332333    public static class BenchmarkData extends CapturingBenchmark {
    333334
    334         private List<StyleRecord> allStyleElems;
     335        private Collection<StyleRecord> allStyleElems;
    335336
    336337        @Override
    337         public boolean renderDraw(List<StyleRecord> allStyleElems) {
     338        public boolean renderDraw(Collection<StyleRecord> allStyleElems) {
    338339            this.allStyleElems = allStyleElems;
    339340            return super.renderDraw(allStyleElems);
    340341        }