Skip to content

Commit 9831776

Browse files
C# performance: further reduce allocations by pooling OutPt, OutRec, reusing Active and tracking OutPt count in outRec (#1024)
* CSharp Performance: when reusing clipper objects, currently clipper sets the internal lists capacity, even if the capacity already sufficient. This causes C# to drop the currently allocated array and create a new, smaller one. This changes check if the capacity is sufficient before setting it to reduces allocations. * Implement object pooling for vertex objects. This reduces allocations and improves performance if a clipper instance or Reusable data container is reused. To minimize impact on main clipping module, the pooling is implemented in a separate class VertexPoolList : PooledList<Vertex>. This collection is a List that pools objects added to it for reuse. Indexing, growing and enumeration implementation is identical to "System.Collections.Generic.List{T}. The pooled list reuses allocated reference objects. Operations are limited to read, add and clear. * Adding conditional compilation for C# Clipper2lib to change the namespace from Clipper2Lib to Clipper2ZLib when USINGZ. This prepares to allow both variants to be packaged and deployed via Nuget. * Reduced allocations by: - tracking the number of OutPt per OutRec to allocate Path64 with correct capacity - reusing OutPt objects by using new derived class OutPtPoolList - reusing HorzJoin objects by using new derived class HorzJoinPoolList - rounding up requested capacity to next power of 2 for all classes derived from abstract PooledList<T> - eliminating boxing for PooledList<T>.GetEnumerator() - reusing Active objects by putting them on a free stack
1 parent 53915c0 commit 9831776

File tree

2 files changed

+227
-48
lines changed

2 files changed

+227
-48
lines changed

CSharp/Clipper2Lib/Clipper.Engine.cs

Lines changed: 86 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ internal enum HorzPosition { Bottom, Middle, Top }
145145
internal class OutRec
146146
{
147147
public int idx;
148+
public int outPtCount;
148149
public OutRec? owner;
149150
public Active? frontEdge;
150151
public Active? backEdge;
@@ -355,13 +356,15 @@ public class ClipperBase
355356
private FillRule _fillrule;
356357
private Active? _actives;
357358
private Active? _sel;
359+
private Stack<Active> _freeActives;
358360
private readonly List<LocalMinima> _minimaList;
359361
private readonly List<IntersectNode> _intersectList;
360362
private readonly VertexPoolList _vertexList;
361-
private readonly List<OutRec> _outrecList;
363+
private readonly OutRecPoolList _outrecList;
362364
private readonly List<long> _scanlineList;
363365
private readonly List<HorzSegment> _horzSegList;
364-
private readonly List<HorzJoin> _horzJoinList;
366+
private readonly HorzJoinPoolList _horzJoinList;
367+
private readonly OutPtPoolList _outPtPool;
365368
private int _currentLocMin;
366369
private long _currentBotY;
367370
private bool _isSortedMinimaList;
@@ -383,10 +386,12 @@ public ClipperBase()
383386
_minimaList = new List<LocalMinima>();
384387
_intersectList = new List<IntersectNode>();
385388
_vertexList = new VertexPoolList();
386-
_outrecList = new List<OutRec>();
389+
_outrecList = new OutRecPoolList();
387390
_scanlineList = new List<long>();
388391
_horzSegList = new List<HorzSegment>();
389-
_horzJoinList = new List<HorzJoin>();
392+
_horzJoinList = new HorzJoinPoolList();
393+
_outPtPool = new OutPtPoolList();
394+
_freeActives = new Stack<Active>();
390395
PreserveCollinear = true;
391396
}
392397

@@ -760,6 +765,8 @@ protected void ClearSolutionOnly()
760765
_outrecList.Clear();
761766
_horzSegList.Clear();
762767
_horzJoinList.Clear();
768+
_outPtPool.Clear();
769+
_freeActives.Clear();
763770
}
764771

765772
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1164,16 +1171,14 @@ private void InsertLocalMinimaIntoAEL(long botY)
11641171
}
11651172
else
11661173
{
1167-
leftBound = new Active
1168-
{
1169-
bot = localMinima.vertex.pt,
1170-
curX = localMinima.vertex.pt.X,
1171-
windDx = -1,
1172-
vertexTop = localMinima.vertex.prev,
1173-
top = localMinima.vertex.prev!.pt,
1174-
outrec = null,
1175-
localMin = localMinima
1176-
};
1174+
leftBound = NewActive();
1175+
leftBound.bot = localMinima.vertex.pt;
1176+
leftBound.curX = localMinima.vertex.pt.X;
1177+
leftBound.windDx = -1;
1178+
leftBound.vertexTop = localMinima.vertex.prev;
1179+
leftBound.top = localMinima.vertex.prev!.pt;
1180+
leftBound.outrec = null;
1181+
leftBound.localMin = localMinima;
11771182
SetDx(leftBound);
11781183
}
11791184

@@ -1184,16 +1189,14 @@ private void InsertLocalMinimaIntoAEL(long botY)
11841189
}
11851190
else
11861191
{
1187-
rightBound = new Active
1188-
{
1189-
bot = localMinima.vertex.pt,
1190-
curX = localMinima.vertex.pt.X,
1191-
windDx = 1,
1192-
vertexTop = localMinima.vertex.next, // i.e. ascending
1193-
top = localMinima.vertex.next!.pt,
1194-
outrec = null,
1195-
localMin = localMinima
1196-
};
1192+
rightBound = NewActive();
1193+
rightBound.bot = localMinima.vertex.pt;
1194+
rightBound.curX = localMinima.vertex.pt.X;
1195+
rightBound.windDx = 1;
1196+
rightBound.vertexTop = localMinima.vertex.next; // i.e. ascending
1197+
rightBound.top = localMinima.vertex.next!.pt;
1198+
rightBound.outrec = null;
1199+
rightBound.localMin = localMinima;
11971200
SetDx(rightBound);
11981201
}
11991202

@@ -1333,7 +1336,7 @@ private OutPt AddLocalMinPoly(Active ae1, Active ae2, Point64 pt, bool isNew = f
13331336
}
13341337
}
13351338

1336-
OutPt op = new OutPt(pt, outrec);
1339+
OutPt op = _outPtPool.Add(pt, outrec);
13371340
outrec.pts = op;
13381341
return op;
13391342
}
@@ -1427,6 +1430,7 @@ private static void JoinOutrecPaths(Active ae1, Active ae2)
14271430
ae2.outrec.frontEdge = null;
14281431
ae2.outrec.backEdge = null;
14291432
ae2.outrec.pts = null;
1433+
ae1.outrec.outPtCount += ae2.outrec.outPtCount;
14301434
SetOwner(ae2.outrec, ae1.outrec);
14311435

14321436
if (IsOpenEnd(ae1))
@@ -1441,7 +1445,7 @@ private static void JoinOutrecPaths(Active ae1, Active ae2)
14411445
}
14421446

14431447
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1444-
private static OutPt AddOutPt(Active ae, Point64 pt)
1448+
private OutPt AddOutPt(Active ae, Point64 pt)
14451449
{
14461450

14471451
// Outrec.OutPts: a circular doubly-linked-list of POutPt where ...
@@ -1459,7 +1463,7 @@ private static OutPt AddOutPt(Active ae, Point64 pt)
14591463
return opBack;
14601464
}
14611465

1462-
OutPt newOp = new OutPt(pt, outrec);
1466+
OutPt newOp = _outPtPool.Add(pt, outrec);
14631467
opBack.prev = newOp;
14641468
newOp.prev = opFront;
14651469
newOp.next = opBack;
@@ -1471,11 +1475,9 @@ private static OutPt AddOutPt(Active ae, Point64 pt)
14711475
[MethodImpl(MethodImplOptions.AggressiveInlining)]
14721476
private OutRec NewOutRec()
14731477
{
1474-
OutRec result = new OutRec
1475-
{
1476-
idx = _outrecList.Count
1477-
};
1478-
_outrecList.Add(result);
1478+
int idx = _outrecList.Count;
1479+
OutRec result = _outrecList.Add();
1480+
result.idx = idx;
14791481
return result;
14801482
}
14811483

@@ -1496,7 +1498,7 @@ private OutPt StartOpenPath(Active ae, Point64 pt)
14961498
}
14971499

14981500
ae.outrec = outrec;
1499-
OutPt op = new OutPt(pt, outrec);
1501+
OutPt op = _outPtPool.Add(pt, outrec);
15001502
outrec.pts = op;
15011503
return op;
15021504
}
@@ -1812,6 +1814,45 @@ private void DeleteFromAEL(Active ae)
18121814
_actives = next;
18131815
if (next != null) next.prevInAEL = prev;
18141816
// delete &ae;
1817+
PoolDeletedActive(ae);
1818+
}
1819+
1820+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1821+
private void PoolDeletedActive(Active ae)
1822+
{
1823+
//clear refs to allow GC
1824+
ae.bot = new Point64();
1825+
ae.top = new Point64();
1826+
ae.dx = 0.0;
1827+
ae.windCount = 0;
1828+
ae.windCount2 = 0;
1829+
ae.outrec = null;
1830+
ae.prevInAEL = null;
1831+
ae.nextInAEL = null;
1832+
ae.prevInSEL = null;
1833+
ae.nextInSEL = null;
1834+
ae.jump = null;
1835+
ae.vertexTop = null;
1836+
ae.localMin = new LocalMinima();
1837+
ae.isLeftBound = false;
1838+
ae.joinWith = JoinWith.None;
1839+
_freeActives.Push(ae);
1840+
}
1841+
1842+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1843+
private Active NewActive()
1844+
{
1845+
Active ae;
1846+
if (_freeActives.Count == 0)
1847+
{
1848+
ae = new Active();
1849+
}
1850+
else
1851+
{
1852+
//recycle active from free list
1853+
ae = _freeActives.Pop();
1854+
}
1855+
return ae;
18151856
}
18161857

18171858
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -2497,9 +2538,9 @@ private static bool UpdateHorzSegment(HorzSegment hs)
24972538
}
24982539

24992540
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2500-
private static OutPt DuplicateOp(OutPt op, bool insert_after)
2541+
private OutPt DuplicateOp(OutPt op, bool insert_after)
25012542
{
2502-
OutPt result = new OutPt(op.pt, op.outrec);
2543+
OutPt result = _outPtPool.Add(op.pt, op.outrec);
25032544
if (insert_after)
25042545
{
25052546
result.next = op.next;
@@ -2556,10 +2597,9 @@ private void ConvertHorzSegsToJoins()
25562597
while (hs2.leftOp.prev.pt.Y == curr_y &&
25572598
hs2.leftOp.prev.pt.X <= hs1.leftOp.pt.X)
25582599
(hs2).leftOp = (hs2).leftOp.prev;
2559-
HorzJoin join = new HorzJoin(
2600+
HorzJoin join = _horzJoinList.Add(
25602601
DuplicateOp((hs1).leftOp, true),
25612602
DuplicateOp((hs2).leftOp, false));
2562-
_horzJoinList.Add(join);
25632603
}
25642604
else
25652605
{
@@ -2569,10 +2609,9 @@ private void ConvertHorzSegsToJoins()
25692609
while (hs2.leftOp.next!.pt.Y == curr_y &&
25702610
hs2.leftOp.next.pt.X <= (hs1).leftOp.pt.X)
25712611
hs2.leftOp = (hs2).leftOp.next;
2572-
HorzJoin join = new HorzJoin(
2612+
HorzJoin join = _horzJoinList.Add(
25732613
DuplicateOp((hs2).leftOp, true),
25742614
DuplicateOp((hs1).leftOp, false));
2575-
_horzJoinList.Add(join);
25762615
}
25772616
}
25782617
}
@@ -2879,7 +2918,9 @@ private void DoSplitOp(OutRec outrec, OutPt splitOp)
28792918
}
28802919
else
28812920
{
2882-
OutPt newOp2 = new OutPt(ip, outrec) { prev = prevOp, next = nextNextOp };
2921+
OutPt newOp2 = _outPtPool.Add(ip, outrec);
2922+
newOp2.prev = prevOp;
2923+
newOp2.next = nextNextOp;
28832924
nextNextOp.prev = newOp2;
28842925
prevOp.next = newOp2;
28852926
}
@@ -2897,7 +2938,9 @@ private void DoSplitOp(OutRec outrec, OutPt splitOp)
28972938
splitOp.outrec = newOutRec;
28982939
splitOp.next.outrec = newOutRec;
28992940

2900-
OutPt newOp = new OutPt(ip, newOutRec) { prev = splitOp.next, next = splitOp };
2941+
OutPt newOp = _outPtPool.Add(ip, newOutRec);
2942+
newOp.prev = splitOp.next;
2943+
newOp.next = splitOp;
29012944
newOutRec.pts = newOp;
29022945
splitOp.prev = newOp;
29032946
splitOp.next.next = newOp;
@@ -3004,7 +3047,7 @@ protected bool BuildPaths(Paths64 solutionClosed, Paths64 solutionOpen)
30043047
OutRec outrec = _outrecList[i++];
30053048
if (outrec.pts == null) continue;
30063049

3007-
Path64 path = new Path64();
3050+
Path64 path = new Path64(outrec.outPtCount);
30083051
if (outrec.isOpen)
30093052
{
30103053
if (BuildPath(outrec.pts, ReverseSolution, true, path))
@@ -3104,7 +3147,7 @@ protected void BuildTree(PolyPathBase polytree, Paths64 solutionOpen)
31043147

31053148
if (outrec.isOpen)
31063149
{
3107-
Path64 open_path = new Path64();
3150+
Path64 open_path = new Path64(outrec.outPtCount);
31083151
if (BuildPath(outrec.pts, ReverseSolution, true, open_path))
31093152
solutionOpen.Add(open_path);
31103153
continue;

0 commit comments

Comments
 (0)