Ticket #17819: 17819-multi-thread-v2.patch

File 17819-multi-thread-v2.patch, 15.2 KB (added by GerdP, 7 years ago)

implement same cache as in MultipolygonBuilder

  • src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java

     
    1616import java.util.Map;
    1717import java.util.Map.Entry;
    1818import java.util.Set;
     19import java.util.concurrent.ForkJoinPool;
     20import java.util.concurrent.ForkJoinTask;
     21import java.util.concurrent.RecursiveTask;
     22import java.util.function.Supplier;
    1923
    2024import org.openstreetmap.josm.command.ChangeCommand;
    2125import org.openstreetmap.josm.command.Command;
     
    3842import org.openstreetmap.josm.tools.Geometry;
    3943import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
    4044import org.openstreetmap.josm.tools.Logging;
     45import org.openstreetmap.josm.tools.Pair;
     46import org.openstreetmap.josm.tools.Utils;
    4147
    4248/**
    4349 * Checks if multipolygons are valid
     
    4450 * @since 3669
    4551 */
    4652public class MultipolygonTest extends Test {
     53    private static final ForkJoinPool THREAD_POOL = newForkJoinPool();
    4754
     55    private static ForkJoinPool newForkJoinPool() {
     56        try {
     57            return Utils.newForkJoinPool(
     58                    "multipolygon_creation.numberOfThreads", "multipolygon-test-%d", Thread.NORM_PRIORITY);
     59        } catch (SecurityException e) {
     60            Logging.log(Logging.LEVEL_ERROR, "Unable to create new ForkJoinPool", e);
     61            return null;
     62        }
     63    }
     64
    4865    /** Non-Way in multipolygon */
    4966    public static final int WRONG_MEMBER_TYPE = 1601;
    5067    /** No useful role for multipolygon member */
     
    463480     * @param sharedNodes all nodes shared by multiple ways of this multipolygon
    464481     */
    465482    private void checkOrSetRoles(Relation r, List<PolyData> allPolygons, Map<Long, RelationMember> wayMap, Set<Node> sharedNodes) {
    466         PolygonLevelFinder levelFinder = new PolygonLevelFinder(sharedNodes);
    467         List<PolygonLevel> list = levelFinder.findOuterWays(allPolygons);
     483        List<PolygonLevel> list = findOuterWaysMultiThread(allPolygons, sharedNodes);
    468484        if (list == null || list.isEmpty()) {
    469485            return;
    470486        }
     
    800816    }
    801817
    802818    /**
     819     * Collects outer way and corresponding inner ways from all rings.
     820     * @param rings the polygon rings
     821     * @param sharedNodes all nodes shared by multiple ways of this multipolygon
     822     * @return list of nesting levels
     823     */
     824    private static List<PolygonLevel> findOuterWaysMultiThread(List<PolyData> rings, Set<Node> sharedNodes) {
     825        final IntersectionMatrix cache = new IntersectionMatrix(rings);
     826        PolygonLevelFinder worker = new PolygonLevelFinder(cache, sharedNodes, rings, 0, rings.size(),
     827                new ArrayList<PolygonLevel>(), 128);
     828        if (THREAD_POOL != null) {
     829            return THREAD_POOL.invoke(worker);
     830        } else {
     831            return worker.computeDirectly();
     832        }
     833    }
     834
     835    /**
     836     * Helper class to avoid unneeded costly intersection calculations.
     837     * If the intersection between polygons a and b was calculated we also know
     838     * the result of intersection between b and a. The lookup in the hash tables is
     839     * much faster than the intersection calculation.
     840     */
     841    private static class IntersectionMatrix {
     842        private final Map<Pair<PolyData, PolyData>, PolygonIntersection> results;
     843
     844        IntersectionMatrix(Collection<PolyData> polygons) {
     845            results = new HashMap<>(Utils.hashMapInitialCapacity(polygons.size() * polygons.size()));
     846        }
     847
     848        /**
     849         * Compute the reverse result of the intersection test done by {@code Geometry.polygonIntersection(Area a1, Area a2)}
     850         *
     851         * @param intersection the intersection result for polygons a1 and a2 (in that order)
     852         * @return the intersection result for a2 and a1
     853         */
     854        private static PolygonIntersection getReverseIntersectionResult(PolygonIntersection intersection) {
     855            switch (intersection) {
     856                case FIRST_INSIDE_SECOND:
     857                    return PolygonIntersection.SECOND_INSIDE_FIRST;
     858                case SECOND_INSIDE_FIRST:
     859                    return PolygonIntersection.FIRST_INSIDE_SECOND;
     860                default:
     861                    return intersection;
     862            }
     863        }
     864
     865        /**
     866         * Returns the precomputed intersection between two polygons if known. Otherwise perform {@code computation}.
     867         *
     868         * @param a1          first polygon
     869         * @param a2          second polygon
     870         * @param computation the computation to perform when intersection is unknown
     871         * @return the intersection between two polygons
     872         * @see Map#computeIfAbsent
     873         */
     874        PolygonIntersection computeIfAbsent(PolyData a1, PolyData a2, Supplier<PolygonIntersection> computation) {
     875            PolygonIntersection intersection = results.get(Pair.create(a1, a2));
     876            if (intersection == null) {
     877                intersection = computation.get();
     878                synchronized (results) {
     879                    results.put(Pair.create(a1, a2), intersection);
     880                    results.put(Pair.create(a2, a1), getReverseIntersectionResult(intersection));
     881                }
     882            }
     883            return intersection;
     884        }
     885
     886    }
     887
     888    /**
    803889     * Find nesting levels of polygons. Logic taken from class MultipolygonBuilder, uses different structures.
    804890     */
    805     private static class PolygonLevelFinder {
    806         private final Set<Node> sharedNodes;
     891    private static class PolygonLevelFinder extends RecursiveTask<List<PolygonLevel>> {
     892        private final transient IntersectionMatrix cache;
     893        private final transient Set<Node> sharedNodes;
     894        private final transient List<PolyData> input;
     895        private final int from;
     896        private final int to;
     897        private final transient List<PolygonLevel> output;
     898        private final int directExecutionTaskSize;
    807899
    808         PolygonLevelFinder(Set<Node> sharedNodes) {
     900        private static final long serialVersionUID = 0;
     901
     902        PolygonLevelFinder(IntersectionMatrix cache, Set<Node> sharedNodes, List<PolyData> input, int from, int to, List<PolygonLevel> output,
     903                int directExecutionTaskSize) {
     904            this.cache = cache;
    809905            this.sharedNodes = sharedNodes;
     906            this.input = input;
     907            this.from = from;
     908            this.to = to;
     909            this.output = output;
     910            this.directExecutionTaskSize = directExecutionTaskSize;
    810911        }
    811912
    812         List<PolygonLevel> findOuterWays(List<PolyData> allPolygons) {
    813             return findOuterWaysRecursive(0, allPolygons);
    814         }
    815 
    816913        private List<PolygonLevel> findOuterWaysRecursive(int level, List<PolyData> polygons) {
    817914            final List<PolygonLevel> result = new ArrayList<>();
    818915
     
    824921        }
    825922
    826923        private void processOuterWay(int level, List<PolyData> polygons, List<PolygonLevel> result, PolyData pd) {
    827             List<PolyData> inners = findInnerWaysCandidates(pd, polygons);
     924            Pair<Boolean, List<PolyData>> res = findInnerWaysForOuterCandidate(pd, polygons);
    828925
    829             if (inners != null) {
     926            if (res.a) {
    830927                //add new outer polygon
    831928                PolygonLevel pol = new PolygonLevel(pd, level);
    832929
    833930                //process inner ways
     931                List<PolyData> inners = res.b;
    834932                if (!inners.isEmpty()) {
    835                     List<PolygonLevel> innerList = findOuterWaysRecursive(level + 1, inners);
    836                     result.addAll(innerList);
     933                    result.addAll(findOuterWaysRecursive(level + 1, inners));
    837934                }
    838935
    839936                result.add(pol);
     
    841938        }
    842939
    843940        /**
    844          * Check if polygon is an out-most ring, if so, collect the inners
     941         * Check if polygon is an out-most ring for all given polygons, if so, collect the inner rings.
    845942         * @param outerCandidate polygon which is checked
    846943         * @param polygons all polygons
    847          * @return null if outerCandidate is inside any other polygon, else a list of inner polygons (which might be empty)
     944         * @return pair, first value is true if outerCandidate is a real outer ring, 2nd value contains the list of inner rings
     945         * which might be empty
    848946         */
    849         private List<PolyData> findInnerWaysCandidates(PolyData outerCandidate, List<PolyData> polygons) {
     947        private Pair<Boolean, List<PolyData>> findInnerWaysForOuterCandidate(PolyData outerCandidate, List<PolyData> polygons) {
    850948            List<PolyData> innerCandidates = new ArrayList<>();
    851 
     949            Boolean outerGood = Boolean.TRUE;
    852950            for (PolyData inner : polygons) {
    853                 if (inner == outerCandidate) {
    854                     continue;
     951                if (inner != outerCandidate && outerCandidate.getBounds().intersects(inner.getBounds())) {
     952                    final PolygonIntersection intersection = cache.computeIfAbsent(inner, outerCandidate,
     953                            () -> getNesting(inner, outerCandidate));
     954                    if (PolygonIntersection.FIRST_INSIDE_SECOND == intersection) {
     955                        innerCandidates.add(inner);
     956                    } else if (PolygonIntersection.SECOND_INSIDE_FIRST == intersection) {
     957                        outerGood = Boolean.FALSE;
     958                        break;
     959                    }
    855960                }
    856                 if (!outerCandidate.getBounds().intersects(inner.getBounds())) {
    857                     continue;
    858                 }
    859                 boolean useIntersectionTest = false;
    860                 Node unsharedOuterNode = null;
    861                 Node unsharedInnerNode = getNonIntersectingNode(outerCandidate, inner);
    862                 if (unsharedInnerNode != null) {
    863                     if (checkIfNodeIsInsidePolygon(unsharedInnerNode, outerCandidate)) {
    864                         innerCandidates.add(inner);
     961            }
     962            return new Pair<>(outerGood, innerCandidates);
     963        }
     964
     965        private PolygonIntersection getNesting(PolyData innerCandidate, PolyData outerCandidate) {
     966            boolean useIntersectionTest = false;
     967            Node unsharedOuterNode = null;
     968            Node unsharedInnerNode = getNonIntersectingNode(outerCandidate, innerCandidate);
     969            if (unsharedInnerNode != null) {
     970                if (checkIfNodeIsInsidePolygon(unsharedInnerNode, outerCandidate)) {
     971                    return PolygonIntersection.FIRST_INSIDE_SECOND;
     972                } else {
     973                    // inner is not inside outerCandidate, check if it contains outerCandidate
     974                    unsharedOuterNode = getNonIntersectingNode(innerCandidate, outerCandidate);
     975                    if (unsharedOuterNode != null) {
     976                        if (checkIfNodeIsInsidePolygon(unsharedOuterNode, innerCandidate)) {
     977                            return PolygonIntersection.SECOND_INSIDE_FIRST; // outer is inside inner
     978                        }
    865979                    } else {
    866                         // inner is not inside outerCandidate, check if it contains outerCandidate
    867                         unsharedOuterNode = getNonIntersectingNode(inner, outerCandidate);
    868                         if (unsharedOuterNode != null) {
    869                             if (checkIfNodeIsInsidePolygon(unsharedOuterNode, inner)) {
    870                                 return null; // outer is inside inner
    871                             }
    872                         } else {
    873                             useIntersectionTest = true;
    874                         }
     980                        useIntersectionTest = true;
    875981                    }
     982                }
     983            } else {
     984                // all nodes of inner are also nodes of outerCandidate
     985                unsharedOuterNode = getNonIntersectingNode(innerCandidate, outerCandidate);
     986                if (unsharedOuterNode == null) {
     987                    return PolygonIntersection.CROSSING; // all nodes shared -> same ways, maybe different direction
    876988                } else {
    877                     // all nodes of inner are also nodes of outerCandidate
    878                     unsharedOuterNode = getNonIntersectingNode(inner, outerCandidate);
    879                     if (unsharedOuterNode == null) {
    880                         return null; // all nodes shared -> same ways, maybe different direction
     989                    if (checkIfNodeIsInsidePolygon(unsharedOuterNode, innerCandidate)) {
     990                        return null; // outer is inside inner
    881991                    } else {
    882                         if (checkIfNodeIsInsidePolygon(unsharedOuterNode, inner)) {
    883                             return null; // outer is inside inner
    884                         } else {
    885                             useIntersectionTest = true;
    886                         }
     992                        useIntersectionTest = true;
    887993                    }
    888994                }
    889                 if (useIntersectionTest) {
    890                     PolygonIntersection res = Geometry.polygonIntersection(inner.getNodes(), outerCandidate.getNodes());
    891                     if (res == PolygonIntersection.FIRST_INSIDE_SECOND)
    892                         innerCandidates.add(inner);
    893                     else if (res == PolygonIntersection.SECOND_INSIDE_FIRST)
    894                         return null;
    895                 }
    896995            }
    897             return innerCandidates;
     996            if (useIntersectionTest) {
     997                return Geometry.polygonIntersection(innerCandidate.getNodes(), outerCandidate.getNodes());
     998            }
     999            return PolygonIntersection.OUTSIDE;
    8981000        }
    8991001
    9001002        /**
     
    9101012            }
    9111013            return null;
    9121014        }
     1015
     1016        @Override
     1017        protected List<PolygonLevel> compute() {
     1018            if (to - from <= directExecutionTaskSize) {
     1019                return computeDirectly();
     1020            } else {
     1021                final Collection<ForkJoinTask<List<PolygonLevel>>> tasks = new ArrayList<>();
     1022                for (int fromIndex = from; fromIndex < to; fromIndex += directExecutionTaskSize) {
     1023                    tasks.add(new PolygonLevelFinder(cache, sharedNodes, input, fromIndex,
     1024                            Math.min(fromIndex + directExecutionTaskSize, to), new ArrayList<PolygonLevel>(),
     1025                            directExecutionTaskSize));
     1026                }
     1027                for (ForkJoinTask<List<PolygonLevel>> task : ForkJoinTask.invokeAll(tasks)) {
     1028                    List<PolygonLevel> res = task.join();
     1029                    output.addAll(res);
     1030                }
     1031                return output;
     1032            }
     1033        }
     1034
     1035        List<PolygonLevel> computeDirectly() {
     1036            for (int i = from; i < to; i++) {
     1037                processOuterWay(0, input, output, input.get(i));
     1038            }
     1039            return output;
     1040        }
    9131041    }
    9141042
    9151043    /**
    9161044     * Create a multipolygon relation from the given ways and test it.
    9171045     * @param ways the collection of ways
    918      * @return a pair of a {@link Multipolygon} instance and the relation.
     1046     * @return the relation, caller should call getErrors() to check if relation is a valid multipolygon
    9191047     * @since 15160
    9201048     */
    9211049    public Relation makeFromWays(Collection<Way> ways) {