-
Notifications
You must be signed in to change notification settings - Fork 0
Benchmark
Performance and correctness benchmarks comparing the GPU-accelerated DiGi.ComputeSharp kernels against equivalent CPU implementations, primarily the native DiGi.Geometry library.
All benchmarks live as [Fact] tests in the DiGi.ComputeSharp.xUnit test project (DiGi.Test/DiGi.ComputeSharp.xUnit/Facts/), so they can be re-run and re-verified at any time rather than treated as a one-off snapshot.
| Component | Specification |
|---|---|
| CPU | AMD Ryzen 9 9950X, 16 cores / 32 threads (Environment.ProcessorCount = 32) |
| GPU | NVIDIA GeForce RTX 5090 (secondary: AMD Radeon integrated graphics) |
| RAM | 61.4 GB |
| OS | Windows 11 Pro (10.0.26200) |
| .NET SDK | 10.0.301 |
| Build config | Release (unless noted) |
Numbers are machine- and driver-specific — re-run the benchmarks on your own hardware before drawing conclusions for a different environment.
File: Facts/Line2IntersectionCrossValidation.cs
Cross-validates the GPU Line2IntersectionComputeShader against DiGi.Geometry.Planar.Query.IntersectionPoint (native CPU) by intersecting one reference segment against N random segments and comparing, per segment, whether both sides agree that a single on-segment crossing exists and, if so, that the point matches within 1e-6.
-
Single editable knob:
Line2Intersection_CrossValidation_SegmentCount(constintat top of the class) — set high (e.g.1_000_000) for a one-off stress run, low (e.g.1_000) for fast everyday runs. - Crossings within a
1e-3guard band of any of the four segment endpoints are skipped: the GPU (perpendicular-distanceOntest) and the native library (parametric[0,1]test) can legitimately disagree in that measure-zero region. - Deterministic seed (
20260702) so a failing stress run is reproducible.
Result at SegmentCount = 1,000,000:
segments=1000000, crossings=498960, misses=501022, ambiguousSkipped=18, collinearSkipped=0, mismatches=0
Outcome: 0 mismatches across ~1M comparisons (500k genuine crossings, 500k misses). The GPU kernel and the native library agree exactly outside the expected endpoint-ambiguity band.
File: Facts/Line2IntersectionPerformance.cs
Benchmarks the same workload (one reference segment vs. N random segments) across a swept list of segment counts, comparing three implementations:
-
GPU —
Line2IntersectionComputeShader, timed end-to-end (buffer upload, dispatch, GPU→CPU read-back). -
CPU, single-threaded —
DiGi.Geometry.Planar.Query.IntersectionPointin a plainforloop. -
CPU, multi-threaded — the same native call via
Parallel.Forwith thread-local accumulation (uses all 32 logical threads on the test machine).
A warm-up pass (256 segments) precedes the measured runs so GPU shader compilation and JIT costs are excluded. The segment-count sweep is editable via the Line2Intersection_Performance_SegmentCounts array at the top of the class. Each run asserts all three back-ends agree on hit count (within the same endpoint-ambiguity band as the correctness test), so timings are guaranteed to measure equivalent work.
| segments | GPU (ms) | CPU 1-thread (ms) | CPU 32-thread (ms) | GPU vs 1T | GPU vs MT |
|---|---|---|---|---|---|
| 1,000 | 4.34 | 0.64 | 0.43 | 0.15× | 0.10× |
| 10,000 | 6.46 | 7.75 | 4.40 | 1.20× | 0.68× |
| 100,000 | 6.89 | 45.95 | 30.68 | 6.67× | 4.45× |
| 1,000,000 | 37.87 | 308.18 | 55.77 | 8.14× | 1.47× |
| segments | GPU (ms) | CPU (ms) | speed-up |
|---|---|---|---|
| 1,000 | 2.40 | 0.58 | 0.24× |
| 10,000 | 2.95 | 5.74 | 1.94× |
| 100,000 | 7.14 | 64.23 | 9.00× |
| 1,000,000 | 40.59 | 469.89 | 11.58× |
Debug flatters the GPU column because the CPU loop is not JIT-optimized (11.58× in Debug vs. 8.14× in Release at 1M) — the Release numbers are the representative ones.
- Fixed GPU overhead (~4–7 ms) — buffer allocation, dispatch, and read-back — dominates below ~10–100k segments. Below that the CPU wins outright, even single-threaded.
- Crossover vs. single-threaded CPU: ~8–10k segments.
- Crossover vs. all-core CPU: shifts much higher; even at 1,000,000 segments the GPU is only 1.47× faster than the 32-thread CPU path (vs. 8.14× against one thread). The GPU's headline advantage largely evaporates once the CPU is allowed to use all cores.
-
Parallel CPU scaling is sub-linear and workload-dependent: 1-thread → 32-thread is only ~1.5× at 100k segments but ~5.5× at 1M. The per-segment work (one 2×2 determinant plus a few comparisons) is small, so at 100k
Parallel.Forpartitioning/scheduling overhead dominates; only at 1M is there enough work per core to amortize it. Scaling never approaches 32× — the workload is memory-bandwidth bound, not compute bound. - Takeaway: the GPU line-line kernel is a poor fit for this specific cheap, low-arithmetic-intensity operation once multi-core CPU is on the table. GPU dispatch pays off more clearly for heavier per-element kernels (e.g. line-vs-triangle, triangle-vs-triangle) or when data can stay resident on the GPU across multiple passes, avoiding the per-call upload/read-back tax that dominates this benchmark.
- Add a
[Fact]underFacts/inDiGi.ComputeSharp.xUnit, gated withQuery.IsComputeSharpSupported(testOutputHelper)and acatch (Exception exception) when (exception.GetType().Name == "UnsupportedDoubleOperationException")for FP64-unsupported GPUs, per the project's existing GPU test convention. - Expose the sweep size(s) as a single
const/static readonlyfield at the top of the class so the benchmark can be scaled up for a one-off stress run and back down for fast everyday runs. - Warm up (small batch) before timing to exclude shader compilation / JIT cost.
- Assert cross-implementation agreement (hit counts / result values) so timing comparisons are guaranteed to cover equivalent work.
- Record the machine spec, fully namespace-qualified method names, the result table, and the analysis on this page.
This page follows the shared DiGi benchmark-page standard. See also the DiGi.Geometry Benchmark page (native CPU query comparisons).