From a2f83fd2bbc26eee3dbe117da6ddcc29a08b164e Mon Sep 17 00:00:00 2001 From: Stefan Nikolei Date: Sun, 6 Jul 2025 09:55:11 +0200 Subject: [PATCH 1/2] Reduce Allocations by removing creation of List = new(4) --- src/PolygonClipper/PolygonClipper.cs | 61 ++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/PolygonClipper/PolygonClipper.cs b/src/PolygonClipper/PolygonClipper.cs index debd915..65ecf0b 100644 --- a/src/PolygonClipper/PolygonClipper.cs +++ b/src/PolygonClipper/PolygonClipper.cs @@ -569,8 +569,12 @@ private static int PossibleIntersection( } // The line segments associated with le1 and le2 overlap. - // TODO: Rewrite this to avoid allocation. - List events = new(4); + SweepEvent? first = null; + SweepEvent? second = null; + SweepEvent? third = null; + SweepEvent? fourth = null; + bool firstSet = false; + bool leftCoincide = le1.Point == le2.Point; bool rightCoincide = le1.OtherEvent.Point == le2.OtherEvent.Point; @@ -579,27 +583,33 @@ private static int PossibleIntersection( { if (comparer.Compare(le1, le2) > 0) { - events.Add(le2); - events.Add(le1); + first = le2; + second = le1; } else { - events.Add(le1); - events.Add(le2); + first = le1; + second = le2; } + + firstSet = true; } if (!rightCoincide) { - if (comparer.Compare(le1.OtherEvent, le2.OtherEvent) > 0) + (SweepEvent? rightFirst, SweepEvent? rightSecond) = comparer.Compare(le1.OtherEvent, le2.OtherEvent) > 0 + ? (le2.OtherEvent, le1.OtherEvent) + : (le1.OtherEvent, le2.OtherEvent); + + if (!firstSet) { - events.Add(le2.OtherEvent); - events.Add(le1.OtherEvent); + first = rightFirst; + second = rightSecond; } else { - events.Add(le1.OtherEvent); - events.Add(le2.OtherEvent); + third = rightFirst; + fourth = rightSecond; } } @@ -613,7 +623,10 @@ private static int PossibleIntersection( if (leftCoincide && !rightCoincide) { - DivideSegment(events[1].OtherEvent, events[0].Point, eventQueue, comparer); + ArgumentNullException.ThrowIfNull(first); + ArgumentNullException.ThrowIfNull(second); + + DivideSegment(second.OtherEvent, first.Point, eventQueue, comparer); } return 2; @@ -622,21 +635,33 @@ private static int PossibleIntersection( // Handle the rightCoincide case if (rightCoincide) { - DivideSegment(events[0], events[1].Point, eventQueue, comparer); + ArgumentNullException.ThrowIfNull(first); + ArgumentNullException.ThrowIfNull(second); + + DivideSegment(first, second.Point, eventQueue, comparer); return 3; } + ArgumentNullException.ThrowIfNull(fourth); + // Handle general overlapping case - if (events[0] != events[3].OtherEvent) + if (first != fourth.OtherEvent) { - DivideSegment(events[0], events[1].Point, eventQueue, comparer); - DivideSegment(events[1], events[2].Point, eventQueue, comparer); + ArgumentNullException.ThrowIfNull(first); + ArgumentNullException.ThrowIfNull(second); + ArgumentNullException.ThrowIfNull(third); + + DivideSegment(first, second.Point, eventQueue, comparer); + DivideSegment(second, third.Point, eventQueue, comparer); return 3; } + ArgumentNullException.ThrowIfNull(second); + ArgumentNullException.ThrowIfNull(third); + // One segment fully contains the other - DivideSegment(events[0], events[1].Point, eventQueue, comparer); - DivideSegment(events[3].OtherEvent, events[2].Point, eventQueue, comparer); + DivideSegment(first, second.Point, eventQueue, comparer); + DivideSegment(fourth.OtherEvent, third.Point, eventQueue, comparer); return 3; } From cdaa25de1b3adeca9ea10881a0b25ff831234556 Mon Sep 17 00:00:00 2001 From: Stefan Nikolei Date: Sat, 12 Jul 2025 20:57:38 +0200 Subject: [PATCH 2/2] Use Span --- src/PolygonClipper/PolygonClipper.cs | 62 ++++++++++++---------------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/src/PolygonClipper/PolygonClipper.cs b/src/PolygonClipper/PolygonClipper.cs index 65ecf0b..58a944d 100644 --- a/src/PolygonClipper/PolygonClipper.cs +++ b/src/PolygonClipper/PolygonClipper.cs @@ -162,6 +162,7 @@ public Polygon Run() SweepEvent? prevEvent; SweepEvent? nextEvent; + Span lineSegmentsList = new SweepEvent[4].AsSpan(); while (eventQueue.Count > 0) { SweepEvent sweepEvent = eventQueue.Dequeue(); @@ -188,7 +189,7 @@ public Polygon Run() if (nextEvent != null) { // Check intersection with the next neighbor - if (PossibleIntersection(sweepEvent, nextEvent, eventQueue) == 2) + if (PossibleIntersection(sweepEvent, nextEvent, eventQueue, lineSegmentsList) == 2) { ComputeFields(sweepEvent, prevEvent, operation); ComputeFields(nextEvent, sweepEvent, operation); @@ -199,7 +200,7 @@ public Polygon Run() if (prevEvent != null) { // Check intersection with the previous neighbor - if (PossibleIntersection(prevEvent, sweepEvent, eventQueue) == 2) + if (PossibleIntersection(prevEvent, sweepEvent, eventQueue, lineSegmentsList) == 2) { SweepEvent? prevPrevEvent = statusLine.Prev(prevEvent.PosSL); ComputeFields(prevEvent, prevPrevEvent, operation); @@ -218,7 +219,7 @@ public Polygon Run() // Check intersection between neighbors if (prevEvent != null && nextEvent != null) { - _ = PossibleIntersection(prevEvent, nextEvent, eventQueue); + _ = PossibleIntersection(prevEvent, nextEvent, eventQueue, lineSegmentsList); } statusLine.RemoveAt(it); @@ -499,6 +500,7 @@ private static bool InResult(SweepEvent sweepEvent, BooleanOperation operation) /// The first sweep event representing a line segment. /// The second sweep event representing a line segment. /// The event queue to add new events to. + /// A Span which will be used to store the associations between the line segments /// /// An integer indicating the result of the intersection: /// @@ -514,7 +516,8 @@ private static bool InResult(SweepEvent sweepEvent, BooleanOperation operation) private static int PossibleIntersection( SweepEvent le1, SweepEvent le2, - StablePriorityQueue eventQueue) + StablePriorityQueue eventQueue, + Span lineSegmentsList) { if (le1.OtherEvent == null || le2.OtherEvent == null) { @@ -569,10 +572,10 @@ private static int PossibleIntersection( } // The line segments associated with le1 and le2 overlap. - SweepEvent? first = null; - SweepEvent? second = null; - SweepEvent? third = null; - SweepEvent? fourth = null; + // lineSegmentsList[0] = null; + // lineSegmentsList[1] = null; + // lineSegmentsList[2] = null; + // lineSegmentsList[3] = null; bool firstSet = false; bool leftCoincide = le1.Point == le2.Point; @@ -583,13 +586,13 @@ private static int PossibleIntersection( { if (comparer.Compare(le1, le2) > 0) { - first = le2; - second = le1; + lineSegmentsList[0] = le2; + lineSegmentsList[1] = le1; } else { - first = le1; - second = le2; + lineSegmentsList[0] = le1; + lineSegmentsList[1] = le2; } firstSet = true; @@ -603,13 +606,13 @@ private static int PossibleIntersection( if (!firstSet) { - first = rightFirst; - second = rightSecond; + lineSegmentsList[0] = rightFirst; + lineSegmentsList[1] = rightSecond; } else { - third = rightFirst; - fourth = rightSecond; + lineSegmentsList[2] = rightFirst; + lineSegmentsList[3] = rightSecond; } } @@ -623,10 +626,8 @@ private static int PossibleIntersection( if (leftCoincide && !rightCoincide) { - ArgumentNullException.ThrowIfNull(first); - ArgumentNullException.ThrowIfNull(second); - DivideSegment(second.OtherEvent, first.Point, eventQueue, comparer); + DivideSegment(lineSegmentsList[1].OtherEvent, lineSegmentsList[0].Point, eventQueue, comparer); } return 2; @@ -635,33 +636,22 @@ private static int PossibleIntersection( // Handle the rightCoincide case if (rightCoincide) { - ArgumentNullException.ThrowIfNull(first); - ArgumentNullException.ThrowIfNull(second); - - DivideSegment(first, second.Point, eventQueue, comparer); + DivideSegment(lineSegmentsList[0], lineSegmentsList[1].Point, eventQueue, comparer); return 3; } - ArgumentNullException.ThrowIfNull(fourth); - // Handle general overlapping case - if (first != fourth.OtherEvent) + if (lineSegmentsList[0] != lineSegmentsList[3].OtherEvent) { - ArgumentNullException.ThrowIfNull(first); - ArgumentNullException.ThrowIfNull(second); - ArgumentNullException.ThrowIfNull(third); - DivideSegment(first, second.Point, eventQueue, comparer); - DivideSegment(second, third.Point, eventQueue, comparer); + DivideSegment(lineSegmentsList[0], lineSegmentsList[1].Point, eventQueue, comparer); + DivideSegment(lineSegmentsList[1], lineSegmentsList[2].Point, eventQueue, comparer); return 3; } - ArgumentNullException.ThrowIfNull(second); - ArgumentNullException.ThrowIfNull(third); - // One segment fully contains the other - DivideSegment(first, second.Point, eventQueue, comparer); - DivideSegment(fourth.OtherEvent, third.Point, eventQueue, comparer); + DivideSegment(lineSegmentsList[0], lineSegmentsList[1].Point, eventQueue, comparer); + DivideSegment(lineSegmentsList[3].OtherEvent, lineSegmentsList[2].Point, eventQueue, comparer); return 3; }