Skip to content

Commit 670cff4

Browse files
author
Michael Conrad
authored
Add additional comp benchmarks (#14)
1 parent 9bb96fc commit 670cff4

8 files changed

Lines changed: 365 additions & 49 deletions

File tree

benchmark/CDT.Comparison.Benchmarks/CDT.Comparison.Benchmarks.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
(e.g., boost_math .NET examples) get picked up as EmbeddedResource
1414
items and cause MSB3822 build errors. -->
1515
<PropertyGroup>
16-
<DefaultItemExcludes>$(DefaultItemExcludes);native\**</DefaultItemExcludes>
16+
<DefaultItemExcludes>$(DefaultItemExcludes);native\**\*.res;native\**\build\**;native\**\target\**</DefaultItemExcludes>
1717
</PropertyGroup>
1818

1919
<!-- CDT.NET (baseline) -->

benchmark/CDT.Comparison.Benchmarks/ComparisonBenchmarks.cs

Lines changed: 113 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5+
using System.Runtime.InteropServices;
56
using BenchmarkDotNet.Attributes;
67
using BenchmarkDotNet.Configs;
78
using CDT;
@@ -16,7 +17,6 @@
1617
using TnPolygon = TriangleNet.Geometry.Polygon;
1718
using TnSegment = TriangleNet.Geometry.Segment;
1819
using TnVertex = TriangleNet.Geometry.Vertex;
19-
using System.Runtime.InteropServices;
2020

2121
// ---------------------------------------------------------------------------
2222
// Shared input reader
@@ -69,7 +69,7 @@ public static int VerticesOnly(double[] xs, double[] ys)
6969
for (int i = 0; i < xs.Length; i++)
7070
verts.Add(new V2d<double>(xs[i], ys[i]));
7171

72-
var cdt = new Triangulation<double>(VertexInsertionOrder.Auto);
72+
var cdt = new Triangulation<double>(VertexInsertionOrder.Auto, IntersectingConstraintEdges.TryResolve, 0.0);
7373
cdt.InsertVertices(verts);
7474
return cdt.Triangles.Length;
7575
}
@@ -84,11 +84,27 @@ public static int Constrained(double[] xs, double[] ys, int[] ev1, int[] ev2)
8484
for (int i = 0; i < ev1.Length; i++)
8585
edges.Add(new CdtEdge(ev1[i], ev2[i]));
8686

87-
var cdt = new Triangulation<double>(VertexInsertionOrder.Auto);
87+
var cdt = new Triangulation<double>(VertexInsertionOrder.Auto, IntersectingConstraintEdges.TryResolve, 0.0);
8888
cdt.InsertVertices(verts);
8989
cdt.InsertEdges(edges);
9090
return cdt.Triangles.Length;
9191
}
92+
93+
public static int Conforming(double[] xs, double[] ys, int[] ev1, int[] ev2)
94+
{
95+
var verts = new List<V2d<double>>(xs.Length);
96+
for (int i = 0; i < xs.Length; i++)
97+
verts.Add(new V2d<double>(xs[i], ys[i]));
98+
99+
var edges = new List<CdtEdge>(ev1.Length);
100+
for (int i = 0; i < ev1.Length; i++)
101+
edges.Add(new CdtEdge(ev1[i], ev2[i]));
102+
103+
var cdt = new Triangulation<double>(VertexInsertionOrder.Auto, IntersectingConstraintEdges.TryResolve, 0.0);
104+
cdt.InsertVertices(verts);
105+
cdt.ConformToEdges(edges);
106+
return cdt.Triangles.Length;
107+
}
92108
}
93109

94110
// ---------------------------------------------------------------------------
@@ -118,7 +134,32 @@ public static int Constrained(double[] xs, double[] ys, int[] ev1, int[] ev2)
118134
for (int i = 0; i < ev1.Length; i++)
119135
polygon.Add(new TnSegment(verts[ev1[i]], verts[ev2[i]]));
120136

121-
return new TnMesher().Triangulate(polygon).Triangles.Count;
137+
return new TnMesher().Triangulate(polygon, new TriangleNet.Meshing.ConstraintOptions()
138+
{
139+
ConformingDelaunay = false,
140+
SegmentSplitting = 2,
141+
Convex = true
142+
}).Triangles.Count;
143+
}
144+
145+
public static int Conforming(double[] xs, double[] ys, int[] ev1, int[] ev2)
146+
{
147+
var polygon = new TnPolygon(xs.Length);
148+
var verts = new TnVertex[xs.Length];
149+
for (int i = 0; i < xs.Length; i++)
150+
{
151+
verts[i] = new TnVertex(xs[i], ys[i]);
152+
polygon.Add(verts[i]);
153+
}
154+
for (int i = 0; i < ev1.Length; i++)
155+
polygon.Add(new TnSegment(verts[ev1[i]], verts[ev2[i]]));
156+
157+
return new TnMesher().Triangulate(polygon, new TriangleNet.Meshing.ConstraintOptions()
158+
{
159+
ConformingDelaunay = true,
160+
SegmentSplitting = 2,
161+
Convex = true
162+
}).Triangles.Count;
122163
}
123164
}
124165

@@ -139,6 +180,7 @@ public static int VerticesOnly(double[] xs, double[] ys)
139180
coords[i] = new NtsCoordinate(xs[i], ys[i]);
140181

141182
var builder = new NetTopologySuite.Triangulate.DelaunayTriangulationBuilder();
183+
builder.Tolerance = 0.0;
142184
builder.SetSites(coords);
143185
return builder.GetTriangles(Gf).NumGeometries;
144186
}
@@ -158,6 +200,7 @@ public static int Conforming(double[] xs, double[] ys, int[] ev1, int[] ev2)
158200
});
159201

160202
var builder = new NetTopologySuite.Triangulate.ConformingDelaunayTriangulationBuilder();
203+
builder.Tolerance = 0.0;
161204
builder.SetSites(new NtsMultiPoint(pts));
162205
builder.Constraints = new NtsMultiLineString(segments);
163206
return builder.GetTriangles(Gf).NumGeometries;
@@ -178,13 +221,20 @@ private static partial int Triangulate(
178221
double[] xs, double[] ys, int nVerts,
179222
int[] ev1, int[] ev2, int nEdges);
180223

224+
[LibraryImport(Lib, EntryPoint = "cdt_conform_d")]
225+
private static partial int Conform(
226+
double[] xs, double[] ys, int nVerts,
227+
int[] ev1, int[] ev2, int nEdges);
228+
181229
public static int VerticesOnly(double[] xs, double[] ys) =>
182230
Triangulate(xs, ys, xs.Length, [], [], 0);
183231

184232
public static int Constrained(double[] xs, double[] ys, int[] ev1, int[] ev2) =>
185233
Triangulate(xs, ys, xs.Length, ev1, ev2, ev1.Length);
186-
}
187234

235+
public static int Conforming(double[] xs, double[] ys, int[] ev1, int[] ev2) =>
236+
Conform(xs, ys, xs.Length, ev1, ev2, ev1.Length);
237+
}
188238

189239
// ---------------------------------------------------------------------------
190240
// Adapter — Spade (Rust via P/Invoke, spade 2.15.0)
@@ -201,13 +251,20 @@ private static partial int SpadeTriangulate(
201251
double[] xs, double[] ys, int nVerts,
202252
int[] ev1, int[] ev2, int nEdges);
203253

254+
[LibraryImport(Lib, EntryPoint = "spade_conform")]
255+
private static partial int SpadeConform(
256+
double[] xs, double[] ys, int nVerts,
257+
int[] ev1, int[] ev2, int nEdges);
258+
204259
public static int VerticesOnly(double[] xs, double[] ys) =>
205260
SpadeTriangulate(xs, ys, xs.Length, [], [], 0);
206261

207262
public static int Constrained(double[] xs, double[] ys, int[] ev1, int[] ev2) =>
208263
SpadeTriangulate(xs, ys, xs.Length, ev1, ev2, ev1.Length);
209-
}
210264

265+
public static int Conforming(double[] xs, double[] ys, int[] ev1, int[] ev2) =>
266+
SpadeConform(xs, ys, xs.Length, ev1, ev2, ev1.Length);
267+
}
211268

212269
// ---------------------------------------------------------------------------
213270
// Adapter — CGAL (C++ via P/Invoke, CGAL 5.x/6.x)
@@ -227,13 +284,20 @@ private static partial int CgalTriangulate(
227284
double[] xs, double[] ys, int nVerts,
228285
int[] ev1, int[] ev2, int nEdges);
229286

287+
[LibraryImport(Lib, EntryPoint = "cgal_conform")]
288+
private static partial int CgalConform(
289+
double[] xs, double[] ys, int nVerts,
290+
int[] ev1, int[] ev2, int nEdges);
291+
230292
public static int VerticesOnly(double[] xs, double[] ys) =>
231293
CgalTriangulate(xs, ys, xs.Length, [], [], 0);
232294

233295
public static int Constrained(double[] xs, double[] ys, int[] ev1, int[] ev2) =>
234296
CgalTriangulate(xs, ys, xs.Length, ev1, ev2, ev1.Length);
235-
}
236297

298+
public static int Conforming(double[] xs, double[] ys, int[] ev1, int[] ev2) =>
299+
CgalConform(xs, ys, xs.Length, ev1, ev2, ev1.Length);
300+
}
237301

238302
// (~2 600 vertices, ~2 600 constraint edges)
239303
// ---------------------------------------------------------------------------
@@ -258,49 +322,71 @@ public void Setup() =>
258322
[BenchmarkCategory("VerticesOnly")]
259323
public int VO_CdtNet() => CdtNetAdapter.VerticesOnly(_xs, _ys);
260324

261-
[Benchmark(Description = "Triangle.NET")]
262-
[BenchmarkCategory("VerticesOnly")]
263-
public int VO_TriangleNet() => TriangleNetAdapter.VerticesOnly(_xs, _ys);
264-
265-
[Benchmark(Description = "NTS")]
266-
[BenchmarkCategory("VerticesOnly")]
267-
public int VO_Nts() => NtsAdapter.VerticesOnly(_xs, _ys);
268-
269325
[Benchmark(Description = "artem-ogre/CDT (C++)")]
270326
[BenchmarkCategory("VerticesOnly")]
271327
public int VO_NativeCdt() => NativeCdtAdapter.VerticesOnly(_xs, _ys);
272328

329+
[Benchmark(Description = "Spade (Rust)")]
330+
[BenchmarkCategory("VerticesOnly")]
331+
public int VO_Spade() => SpadeAdapter.VerticesOnly(_xs, _ys);
332+
273333
[Benchmark(Description = "CGAL (C++)")]
274334
[BenchmarkCategory("VerticesOnly")]
275335
public int VO_Cgal() => CgalAdapter.VerticesOnly(_xs, _ys);
276336

277-
[Benchmark(Description = "Spade (Rust)")]
337+
[Benchmark(Description = "NTS")]
278338
[BenchmarkCategory("VerticesOnly")]
279-
public int VO_Spade() => SpadeAdapter.VerticesOnly(_xs, _ys);
339+
public int VO_Nts() => NtsAdapter.VerticesOnly(_xs, _ys);
340+
341+
[Benchmark(Description = "Triangle.NET")]
342+
[BenchmarkCategory("VerticesOnly")]
343+
public int VO_TriangleNet() => TriangleNetAdapter.VerticesOnly(_xs, _ys);
280344

281345
// -- Constrained ---------------------------------------------------------
282346

283347
[Benchmark(Baseline = true, Description = "CDT.NET")]
284348
[BenchmarkCategory("Constrained")]
285349
public int CDT_CdtNet() => CdtNetAdapter.Constrained(_xs, _ys, _ev1, _ev2);
286350

287-
[Benchmark(Description = "Triangle.NET")]
288-
[BenchmarkCategory("Constrained")]
289-
public int CDT_TriangleNet() => TriangleNetAdapter.Constrained(_xs, _ys, _ev1, _ev2);
290-
291-
[Benchmark(Description = "NTS (Conforming CDT)")]
292-
[BenchmarkCategory("Constrained")]
293-
public int CDT_Nts() => NtsAdapter.Conforming(_xs, _ys, _ev1, _ev2);
294-
295351
[Benchmark(Description = "artem-ogre/CDT (C++)")]
296352
[BenchmarkCategory("Constrained")]
297353
public int CDT_NativeCdt() => NativeCdtAdapter.Constrained(_xs, _ys, _ev1, _ev2);
298354

355+
[Benchmark(Description = "Spade (Rust)")]
356+
[BenchmarkCategory("Constrained")]
357+
public int CDT_Spade() => SpadeAdapter.Constrained(_xs, _ys, _ev1, _ev2);
358+
299359
[Benchmark(Description = "CGAL (C++)")]
300360
[BenchmarkCategory("Constrained")]
301361
public int CDT_Cgal() => CgalAdapter.Constrained(_xs, _ys, _ev1, _ev2);
302362

303-
[Benchmark(Description = "Spade (Rust)")]
363+
[Benchmark(Description = "Triangle.NET")]
304364
[BenchmarkCategory("Constrained")]
305-
public int CDT_Spade() => SpadeAdapter.Constrained(_xs, _ys, _ev1, _ev2);
365+
public int CDT_TriangleNet() => TriangleNetAdapter.Constrained(_xs, _ys, _ev1, _ev2);
366+
367+
// - Conforming ----------------------------------------------------------
368+
369+
[Benchmark(Baseline = true, Description = "CDT.NET")]
370+
[BenchmarkCategory("Conforming")]
371+
public int CfDT_CdtNet() => CdtNetAdapter.Conforming(_xs, _ys, _ev1, _ev2);
372+
373+
[Benchmark(Description = "artem-ogre/CDT (C++)")]
374+
[BenchmarkCategory("Conforming")]
375+
public int CfDT_NativeCdt() => NativeCdtAdapter.Conforming(_xs, _ys, _ev1, _ev2);
376+
377+
[Benchmark(Description = "Spade (Rust)")]
378+
[BenchmarkCategory("Conforming")]
379+
public int CfDT_Spade() => SpadeAdapter.Conforming(_xs, _ys, _ev1, _ev2);
380+
381+
[Benchmark(Description = "CGAL (C++)")]
382+
[BenchmarkCategory("Conforming")]
383+
public int CfDT_Cgal() => CgalAdapter.Conforming(_xs, _ys, _ev1, _ev2);
384+
385+
[Benchmark(Description = "NTS")]
386+
[BenchmarkCategory("Conforming")]
387+
public int CfDT_Nts() => NtsAdapter.Conforming(_xs, _ys, _ev1, _ev2);
388+
389+
[Benchmark(Description = "Triangle.NET")]
390+
[BenchmarkCategory("Conforming")]
391+
public int CfDT_TriangleNet() => TriangleNetAdapter.Conforming(_xs, _ys, _ev1, _ev2);
306392
}

benchmark/CDT.Comparison.Benchmarks/Program.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,30 @@
44

55
using BenchmarkDotNet.Running;
66

7+
var bench = new ComparisonBenchmarks();
8+
bench.Setup();
9+
10+
static void Print(string label, Func<int> compute) =>
11+
Console.WriteLine($" {label,-22} {compute(),6:N0} triangles");
12+
13+
const int lineWidth = 38;
14+
Console.WriteLine("Constrained Delaunay Triangulation");
15+
Console.WriteLine(new string('-', lineWidth));
16+
Print("CDT.NET", bench.CDT_CdtNet);
17+
Print("artem-ogre/CDT (C++)", bench.CDT_NativeCdt);
18+
Print("Spade (Rust)", bench.CDT_Spade);
19+
Print("CGAL (C++)", bench.CDT_Cgal);
20+
Print("Triangle.NET", bench.CDT_TriangleNet);
21+
Console.WriteLine(new string('-', lineWidth));
22+
23+
Console.WriteLine("Conforming Delaunay Triangulation");
24+
Console.WriteLine(new string('-', lineWidth));
25+
Print("CDT.NET", bench.CfDT_CdtNet);
26+
Print("artem-ogre/CDT (C++)", bench.CfDT_NativeCdt);
27+
Print("Spade (Rust)", bench.CfDT_Spade);
28+
Print("CGAL (C++)", bench.CfDT_Cgal);
29+
Print("NTS", bench.CfDT_Nts);
30+
Print("Triangle.NET", bench.CfDT_TriangleNet);
31+
Console.WriteLine(new string('-', lineWidth));
32+
733
BenchmarkSwitcher.FromAssembly(typeof(ComparisonBenchmarks).Assembly).Run(args);

benchmark/CDT.Comparison.Benchmarks/README.md

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -88,30 +88,57 @@ Results are written to `BenchmarkDotNet.Artifacts/` in the current directory.
8888
| CGAL (C++) | `number_of_faces()` counts all finite triangles in the triangulation, consistent with artem-ogre/CDT. First build downloads CGAL 6.1.1 library headers (~10 MB) and the required Boost sub-library headers (~10 MB ZIPs, ~60 MB staged); subsequent builds use the cmake cache. |
8989
| Spade (Rust) | `num_inner_faces()` returns only inner (non-convex-hull) triangles, which is fewer than the C++ CDT counts |
9090

91+
## Counts of triangles by library and category
92+
93+
```
94+
Constrained Delaunay Triangulation
95+
--------------------------------------
96+
CDT.NET 5.239 triangles
97+
artem-ogre/CDT (C++) 5.239 triangles
98+
Spade (Rust) 5.204 triangles
99+
CGAL (C++) 5.204 triangles
100+
Triangle.NET 5.204 triangles
101+
--------------------------------------
102+
Conforming Delaunay Triangulation
103+
--------------------------------------
104+
CDT.NET 5.343 triangles
105+
artem-ogre/CDT (C++) 5.343 triangles
106+
Spade (Rust) 5.308 triangles
107+
CGAL (C++) 5.324 triangles
108+
NTS 5.764 triangles
109+
Triangle.NET 5.204 triangles
110+
--------------------------------------
111+
```
112+
91113
## Benchmark results
92114

93115
> 12th Gen Intel Core i7-12700KF 3.60GHz, 1 CPU, 20 logical and 12 physical cores
94116
95-
| Method | Categories | Mean | Error | StdDev | Ratio | RatioSD |
96-
|----------------------- |------------- |----------:|----------:|----------:|------:|--------:|
97-
| **CDT.NET** | Constrained | 1.198 ms | 0.1065 ms | 0.0058 ms | 1.00 | 0.01 |
98-
| Triangle.NET | Constrained | 4.571 ms | 2.2827 ms | 0.1251 ms | 3.82 | 0.09 |
99-
| 'NTS (Conforming CDT)' | Constrained | 37.066 ms | 8.5571 ms | 0.4690 ms | 30.95 | 0.36 |
100-
| 'artem-ogre/CDT (C++)' | Constrained | 1.788 ms | 0.1284 ms | 0.0070 ms | 1.49 | 0.01 |
101-
| 'CGAL (C++)' | Constrained | 2.538 ms | 0.0574 ms | 0.0031 ms | 2.12 | 0.01 |
102-
| 'Spade (Rust)' | Constrained | 1.255 ms | 0.1050 ms | 0.0058 ms | 1.05 | 0.01 |
103-
| | | | | | | |
104-
| **CDT.NET** | VerticesOnly | 1.048 ms | 0.0371 ms | 0.0020 ms | 1.00 | 0.00 |
105-
| Triangle.NET | VerticesOnly | 1.323 ms | 0.1856 ms | 0.0102 ms | 1.26 | 0.01 |
106-
| NTS | VerticesOnly | 5.519 ms | 2.8885 ms | 0.1583 ms | 5.26 | 0.13 |
107-
| 'CGAL (C++)' | VerticesOnly | 2.063 ms | 0.2154 ms | 0.0118 ms | 1.97 | 0.01 |
108-
| 'artem-ogre/CDT (C++)' | VerticesOnly | 1.557 ms | 0.1013 ms | 0.0056 ms | 1.49 | 0.01 |
109-
| 'Spade (Rust)' | VerticesOnly | 1.028 ms | 0.0803 ms | 0.0044 ms | 0.98 | 0.00 |
110-
117+
| Method | Categories | Mean | Error | StdDev | Ratio |
118+
|----------------------- |------------- |----------:|-----------:|----------:|------:|
119+
| CDT.NET | Conforming | 1.442 ms | 0.1628 ms | 0.0089 ms | 1.00 |
120+
| 'artem-ogre/CDT (C++)' | Conforming | 1.976 ms | 0.0501 ms | 0.0027 ms | 1.37 |
121+
| 'Spade (Rust)' | Conforming | 1.341 ms | 0.2933 ms | 0.0161 ms | 0.93 |
122+
| 'CGAL (C++)' | Conforming | 4.110 ms | 0.3934 ms | 0.0216 ms | 2.85 |
123+
| NTS | Conforming | 38.288 ms | 39.8335 ms | 2.1834 ms | 26.55 |
124+
| Triangle.NET | Conforming | 3.284 ms | 0.6901 ms | 0.0378 ms | 2.28 |
125+
| | | | | | |
126+
| CDT.NET | Constrained | 1.167 ms | 0.0737 ms | 0.0040 ms | 1.00 |
127+
| 'artem-ogre/CDT (C++)' | Constrained | 1.766 ms | 0.0619 ms | 0.0034 ms | 1.51 |
128+
| 'Spade (Rust)' | Constrained | 1.256 ms | 0.1233 ms | 0.0068 ms | 1.08 |
129+
| 'CGAL (C++)' | Constrained | 2.613 ms | 0.3773 ms | 0.0207 ms | 2.24 |
130+
| Triangle.NET | Constrained | 3.290 ms | 1.3341 ms | 0.0731 ms | 2.82 |
131+
| | | | | | |
132+
| CDT.NET | VerticesOnly | 1.072 ms | 0.0045 ms | 0.0002 ms | 1.00 |
133+
| 'artem-ogre/CDT (C++)' | VerticesOnly | 1.568 ms | 0.2550 ms | 0.0140 ms | 1.46 |
134+
| 'Spade (Rust)' | VerticesOnly | 1.038 ms | 0.0224 ms | 0.0012 ms | 0.97 |
135+
| 'CGAL (C++)' | VerticesOnly | 2.156 ms | 0.2064 ms | 0.0113 ms | 2.01 |
136+
| NTS | VerticesOnly | 5.608 ms | 2.5000 ms | 0.1370 ms | 5.23 |
137+
| Triangle.NET | VerticesOnly | 1.355 ms | 0.0418 ms | 0.0023 ms | 1.26 |
111138

112139
### Key takeaways
113140

114-
- **CDT.NET matches the original C++ implementation (artem-ogre/CDT) and Spade within ≤13%** on both constrained and unconstrained triangulation.
141+
- **CDT.NET matches the original C++ implementation (artem-ogre/CDT) and Spade within ≤13%**.
115142
- **CGAL** runs at ~2× CDT.NET. CGAL's `Constrained_Delaunay_triangulation_2` uses a more complex data structure (half-edge DCEL) with additional bookkeeping overhead vs. CDT.NET's compact flat arrays. For raw triangulation throughput CDT.NET is faster.
116143
- **CDT.NET allocates 5–120× less managed memory** than Triangle.NET and NTS: Triangle.NET allocates ~5.7× more, NTS ~121× more.
117144
- **NTS (conforming CDT)** is ~30× slower and allocates ~120× more memory — Steiner-point insertion is the main cost, and the result is semantically different (not true CDT).

0 commit comments

Comments
 (0)