Skip to content

Benchmark

Jakub Ziolkowski edited this page Jul 3, 2026 · 3 revisions

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.


Test machine spec

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.


Line2 GPU vs. native CPU correctness — Line2Intersection_GpuVsNative

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 (const int at 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-3 guard band of any of the four segment endpoints are skipped: the GPU (perpendicular-distance On test) 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.


Line2 GPU vs. native CPU speed — Line2Intersection_Performance

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:

  1. GPULine2IntersectionComputeShader, timed end-to-end (buffer upload, dispatch, GPU→CPU read-back).
  2. CPU, single-threadedDiGi.Geometry.Planar.Query.IntersectionPoint in a plain for loop.
  3. CPU, multi-threaded — the same native call via Parallel.For with 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.

Results (Release build)

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×

Results (Debug build, single-thread CPU only, for reference)

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.

Analysis

  • 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.For partitioning/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.

Adding a new benchmark

  1. Add a [Fact] under Facts/ in DiGi.ComputeSharp.xUnit, gated with Query.IsComputeSharpSupported(testOutputHelper) and a catch (Exception exception) when (exception.GetType().Name == "UnsupportedDoubleOperationException") for FP64-unsupported GPUs, per the project's existing GPU test convention.
  2. Expose the sweep size(s) as a single const/static readonly field 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.
  3. Warm up (small batch) before timing to exclude shader compilation / JIT cost.
  4. Assert cross-implementation agreement (hit counts / result values) so timing comparisons are guaranteed to cover equivalent work.
  5. 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).

Clone this wiki locally