Opened 10 months ago
Closed 10 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 )
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:
CPU | Memory Allocations | Total 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 693 693 * @since 15938 694 694 */ 695 695 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 } 696 704 Area inter = new Area(a1); 697 705 inter.intersect(a2); 698 706 699 707 if (inter.isEmpty() || !checkIntersection(inter, eps)) { 700 708 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)) { 702 710 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)) { 704 712 return new Pair<>(PolygonIntersection.SECOND_INSIDE_FIRST, inter); 705 713 } else { 706 714 return new Pair<>(PolygonIntersection.CROSSING, inter);
Attachments (0)
Change History (6)
comment:1 by , 10 months ago
Description: | modified (diff) |
---|
comment:2 by , 10 months ago
comment:3 by , 10 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:5 by , 10 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.
That's interesting. I thought area.intersect() always performs the boundary check first.