Modify

Opened 3 months ago

Closed 3 months ago

#23472 closed enhancement (fixed)

[PATCH] Decrease cost of Geometry#polygonIntersectionResult (specifically for geometry.mapcss)

Reported by: taylor.smock Owned by: taylor.smock
Priority: normal Milestone: 24.02
Component: Core Version:
Keywords: performance Cc:

Description (last modified by taylor.smock)

When profiling the validator with an overpass download of Mesa County, Colorado, ~50% of the total CPU time are taken up by Geometry#polygonIntersectionResult. With the attached patch, the total CPU time for that method are reduced to ~7% of the total CPU cycles.

Specific measurements:

CPUMemory AllocationsTotal Validator Time
No patch 522,778ms 54.13 GB ~840s
Patch 22,581ms 1.13 GB ~210s
Difference -500,197ms -53 GB -630s
Difference % -95.7% -97.9% -77.7%
  • src/org/openstreetmap/josm/tools/Geometry.java

    diff --git a/src/org/openstreetmap/josm/tools/Geometry.java b/src/org/openstreetmap/josm/tools/Geometry.java
    a b  
    693693     * @since 15938
    694694     */
    695695    public static Pair<PolygonIntersection, Area> polygonIntersectionResult(Area a1, Area a2, double eps) {
     696        // Simple intersect check (if their bounds don't intersect, don't bother going further; there will be no intersection)
     697        // This avoids the more expensive Area#intersect call some of the time (decreases CPU and memory allocation by ~95%)
     698        // in Mesa County, CO geometry validator test runs.
     699        final Rectangle2D a12d = a1.getBounds2D();
     700        final Rectangle2D a22d = a2.getBounds2D();
     701        if (!a12d.intersects(a22d) || !a1.intersects(a22d) || !a2.intersects(a12d)) {
     702            return new Pair<>(PolygonIntersection.OUTSIDE, new Area());
     703        }
    696704        Area inter = new Area(a1);
    697705        inter.intersect(a2);
    698706
    699707        if (inter.isEmpty() || !checkIntersection(inter, eps)) {
    700708            return new Pair<>(PolygonIntersection.OUTSIDE, inter);
    701         } else if (a2.getBounds2D().contains(a1.getBounds2D()) && inter.equals(a1)) {
     709        } else if (a22d.contains(a12d) && inter.equals(a1)) {
    702710            return new Pair<>(PolygonIntersection.FIRST_INSIDE_SECOND, inter);
    703         } else if (a1.getBounds2D().contains(a2.getBounds2D()) && inter.equals(a2)) {
     711        } else if (a12d.contains(a22d) && inter.equals(a2)) {
    704712            return new Pair<>(PolygonIntersection.SECOND_INSIDE_FIRST, inter);
    705713        } else {
    706714            return new Pair<>(PolygonIntersection.CROSSING, inter);

Attachments (0)

Change History (6)

comment:1 by taylor.smock, 3 months ago

Description: modified (diff)

comment:2 by GerdP, 3 months ago

That's interesting. I thought area.intersect() always performs the boundary check first.

comment:3 by taylor.smock, 3 months ago

Nope; Area#intersect always attempts to calculate the intersection, even if there is no intersection.
The JDK probably should check to see if there actually is an intersection, but I can see arguments for and against that. It would probably have even better performance characteristics if it were done in the JDK (by avoiding at least two instantiations of a Rectangle2D object).

If I were to do it in the JDK, Area#intersect would look something like this:

    public void intersect(Area rhs) {
        final var lhsBounds = this.getCachedBounds();
        final var rhsBounds = rhs.getCachedBounds();
        if (!lhsBounds.intersects(rhsBounds) || !this.intersects(rhsBounds) || !rhs.intersects(lhsBounds)) {
            curves = EmptyCurves;
        } else {
            curves = new AreaOp.IntOp().calculate(this.curves, rhs.curves);
        }
        invalidateBounds();
    }

comment:4 by GerdP, 3 months ago

Right, only area.intersects() checks the boundaries first.

comment:5 by taylor.smock, 3 months ago

I just sent an email to client-libs-dev; maybe they'll make some improvements in the JDK.

In any case, I'll apply the patch next week -- I doubt that the JDK will be modified to sanity check bounds prior to doing the more expensive calculation.

comment:6 by taylor.smock, 3 months ago

Resolution: fixed
Status: assignedclosed

In 18990/josm:

Fix #23472: Decrease cost of Geometry#polygonIntersectionResult (specifically for geometry.mapcss)

When profiling the validator with an overpass download of Mesa County, Colorado,
~50% of the total CPU time are taken up by Geometry#polygonIntersectionResult.

Specifically (inside the function), the improvements for this patch are as follows:

  • CPU cycles: -95.7%
  • Memory allocations: -97.9%

When taken as a whole, the total time taken by the validator was reduced by ~75%.

Modify Ticket

Change Properties
Set your email in Preferences
Action
as closed The owner will remain taylor.smock.
as The resolution will be set.
The resolution will be deleted. Next status will be 'reopened'.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.