-
Notifications
You must be signed in to change notification settings - Fork 2
Performance
Full cross-runtime benchmark results for the Purview Telemetry Source Generator.
Results are generated by BenchmarkDotNet from the benchmark project in the benchmarks/ directory. To reproduce them:
dotnet run --project benchmarks/Purview.Telemetry.Benchmarks/Purview.Telemetry.Benchmarks.csproj \
--configuration Release --framework net10.0Results are written to BenchmarkDotNet.Artifacts/results/ as *-report-github.md, *.csv, and *.html.
See README.md § Performance for a condensed summary of the key .NET 10.0 numbers.
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8037/25H2/2025Update/HudsonValley2)
13th Gen Intel Core i9-13900KF 3.00GHz, 1 CPU, 32 logical and 24 physical cores
.NET SDK 10.0.200
.NET 10.0 : .NET 10.0.5 (10.0.5, 10.0.526.15411), X64 RyuJIT x86-64-v3
.NET 9.0 : .NET 9.0.14 (9.0.14, 9.0.1426.11910), X64 RyuJIT x86-64-v3
.NET 8.0 : .NET 8.0.25 (8.0.25, 8.0.2526.11203), X64 RyuJIT x86-64-v3
.NET FW 4.7: .NET Framework 4.8.1 (4.8.9325.0), X64 RyuJIT VectorSize=256
.NET FW 4.8: .NET Framework 4.8.1 (4.8.9325.0), X64 RyuJIT VectorSize=256
Source: ActivityBenchmarks
Compares the source-generator-produced ActivityOnlyTelemetryCore against a hand-written ManualActivityTelemetry under two conditions: no listener registered (fast-path) and a full-sampling ActivityListener active (production path).
| Method | Runtime | HasListener | Mean | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|
| Manual: start + complete | .NET 10.0 | False | 0.58 ns | 1.00 | - | NA |
| Generated: start + complete | .NET 10.0 | False | 0.54 ns | 0.93 | - | NA |
| Manual: start + fail | .NET 10.0 | False | 0.74 ns | 1.29 | - | NA |
| Generated: start + fail | .NET 10.0 | False | 0.56 ns | 0.97 | - | NA |
| Manual: start + complete | .NET 8.0 | False | 0.77 ns | 1.00 | - | NA |
| Generated: start + complete | .NET 8.0 | False | 0.92 ns | 1.20 | - | NA |
| Manual: start + fail | .NET 8.0 | False | 0.91 ns | 1.19 | - | NA |
| Generated: start + fail | .NET 8.0 | False | 0.93 ns | 1.21 | - | NA |
| Manual: start + complete | .NET 9.0 | False | 0.57 ns | 1.00 | - | NA |
| Generated: start + complete | .NET 9.0 | False | 0.55 ns | 0.97 | - | NA |
| Manual: start + fail | .NET 9.0 | False | 0.74 ns | 1.30 | - | NA |
| Generated: start + fail | .NET 9.0 | False | 0.73 ns | 1.28 | - | NA |
| Manual: start + complete | .NET FW 4.7 | False | 15.44 ns | 1.00 | - | NA |
| Generated: start + complete | .NET FW 4.7 | False | 15.84 ns | 1.03 | - | NA |
| Manual: start + fail | .NET FW 4.7 | False | 15.86 ns | 1.03 | - | NA |
| Generated: start + fail | .NET FW 4.7 | False | 15.83 ns | 1.03 | - | NA |
| Manual: start + complete | .NET FW 4.8 | False | 15.91 ns | 1.00 | - | NA |
| Generated: start + complete | .NET FW 4.8 | False | 16.12 ns | 1.01 | - | NA |
| Manual: start + fail | .NET FW 4.8 | False | 16.00 ns | 1.01 | - | NA |
| Generated: start + fail | .NET FW 4.8 | False | 15.84 ns | 1.00 | - | NA |
| Manual: start + complete | .NET 10.0 | True | 220.29 ns | 1.00 | 1008 B | 1.00 |
| Generated: start + complete | .NET 10.0 | True | 221.85 ns | 1.01 | 1008 B | 1.00 |
| Manual: start + fail | .NET 10.0 | True | 209.87 ns | 0.95 | 920 B | 0.91 |
| Generated: start + fail | .NET 10.0 | True | 196.21 ns | 0.89 | 920 B | 0.91 |
| Manual: start + complete | .NET 8.0 | True | 246.06 ns | 1.00 | 1008 B | 1.00 |
| Generated: start + complete | .NET 8.0 | True | 245.10 ns | 1.00 | 1008 B | 1.00 |
| Manual: start + fail | .NET 8.0 | True | 236.06 ns | 0.96 | 920 B | 0.91 |
| Generated: start + fail | .NET 8.0 | True | 232.77 ns | 0.95 | 920 B | 0.91 |
| Manual: start + complete | .NET 9.0 | True | 233.44 ns | 1.00 | 1008 B | 1.00 |
| Generated: start + complete | .NET 9.0 | True | 223.25 ns | 0.96 | 1008 B | 1.00 |
| Manual: start + fail | .NET 9.0 | True | 213.29 ns | 0.91 | 920 B | 0.91 |
| Generated: start + fail | .NET 9.0 | True | 210.81 ns | 0.90 | 920 B | 0.91 |
| Manual: start + complete | .NET FW 4.7 | True | 553.14 ns | 1.00 | 1268 B | 1.00 |
| Generated: start + complete | .NET FW 4.7 | True | 557.33 ns | 1.01 | 1268 B | 1.00 |
| Manual: start + fail | .NET FW 4.7 | True | 513.81 ns | 0.93 | 1179 B | 0.93 |
| Generated: start + fail | .NET FW 4.7 | True | 507.10 ns | 0.92 | 1179 B | 0.93 |
| Manual: start + complete | .NET FW 4.8 | True | 559.93 ns | 1.00 | 1268 B | 1.00 |
| Generated: start + complete | .NET FW 4.8 | True | 546.71 ns | 0.98 | 1268 B | 1.00 |
| Manual: start + fail | .NET FW 4.8 | True | 514.79 ns | 0.92 | 1179 B | 0.93 |
| Generated: start + fail | .NET FW 4.8 | True | 508.83 ns | 0.91 | 1179 B | 0.93 |
Interpretation: Generated activities are within ±2% of hand-written code and allocate identically across all supported runtimes.
Source: LoggerBenchmarks
Compares three logging approaches: hand-written LoggerMessage.Define (gold-standard manual), generated v1 (LoggerMessage.Define pattern), and generated v2 (state-based ThreadLocalState pattern).
| Method | Runtime | HasLogging | Mean | Ratio | Allocated |
|---|---|---|---|---|---|
| Manual: LoggerMessage.Define — single Info | .NET 10.0 | False | 0.171 ns | 1.00 | - |
| Generated v1 — single Info | .NET 10.0 | False | 0.193 ns | 1.13 | - |
| Generated v2 — single Info | .NET 10.0 | False | 0.197 ns | 1.16 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 10.0 | False | 0.337 ns | 1.97 | - |
| Generated v1 — full lifecycle | .NET 10.0 | False | 0.785 ns | 4.60 | - |
| Generated v2 — full lifecycle | .NET 10.0 | False | 0.772 ns | 4.52 | - |
| Manual: LoggerMessage.Define — single Info | .NET 8.0 | False | 0.180 ns | 1.00 | - |
| Generated v1 — single Info | .NET 8.0 | False | 0.344 ns | 1.92 | - |
| Generated v2 — single Info | .NET 8.0 | False | 0.380 ns | 2.12 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 8.0 | False | 0.971 ns | 5.43 | - |
| Generated v1 — full lifecycle | .NET 8.0 | False | 1.303 ns | 7.28 | - |
| Generated v2 — full lifecycle | .NET 8.0 | False | 1.432 ns | 8.01 | - |
| Manual: LoggerMessage.Define — single Info | .NET 9.0 | False | 0.173 ns | 1.00 | - |
| Generated v1 — single Info | .NET 9.0 | False | 0.171 ns | 0.98 | - |
| Generated v2 — single Info | .NET 9.0 | False | 0.193 ns | 1.12 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 9.0 | False | 0.672 ns | 3.88 | - |
| Generated v1 — full lifecycle | .NET 9.0 | False | 1.098 ns | 6.34 | - |
| Generated v2 — full lifecycle | .NET 9.0 | False | 1.416 ns | 8.18 | - |
| Manual: LoggerMessage.Define — single Info | .NET FW 4.7 | False | 1.520 ns | 1.00 | - |
| Generated v1 — single Info | .NET FW 4.7 | False | 1.757 ns | 1.16 | - |
| Generated v2 — single Info | .NET FW 4.7 | False | 1.812 ns | 1.19 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET FW 4.7 | False | 8.448 ns | 5.56 | - |
| Generated v1 — full lifecycle | .NET FW 4.7 | False | 10.228 ns | 6.73 | - |
| Generated v2 — full lifecycle | .NET FW 4.7 | False | 10.618 ns | 6.99 | - |
| Manual: LoggerMessage.Define — single Info | .NET FW 4.8 | False | 1.514 ns | 1.00 | - |
| Generated v1 — single Info | .NET FW 4.8 | False | 1.848 ns | 1.22 | - |
| Generated v2 — single Info | .NET FW 4.8 | False | 1.783 ns | 1.18 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET FW 4.8 | False | 8.904 ns | 5.90 | - |
| Generated v1 — full lifecycle | .NET FW 4.8 | False | 10.258 ns | 6.79 | - |
| Generated v2 — full lifecycle | .NET FW 4.8 | False | 10.378 ns | 6.87 | - |
| Manual: LoggerMessage.Define — single Info | .NET 10.0 | True | 4.39 ns | 1.00 | - |
| Generated v1 — single Info | .NET 10.0 | True | 4.47 ns | 1.02 | - |
| Generated v2 — single Info | .NET 10.0 | True | 4.16 ns | 0.95 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 10.0 | True | 17.30 ns | 3.94 | - |
| Generated v1 — full lifecycle | .NET 10.0 | True | 17.78 ns | 4.05 | - |
| Generated v2 — full lifecycle | .NET 10.0 | True | 19.10 ns | 4.35 | - |
| Manual: LoggerMessage.Define — single Info | .NET 8.0 | True | 8.31 ns | 1.00 | - |
| Generated v1 — single Info | .NET 8.0 | True | 8.34 ns | 1.00 | - |
| Generated v2 — single Info | .NET 8.0 | True | 7.95 ns | 0.96 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 8.0 | True | 30.34 ns | 3.65 | - |
| Generated v1 — full lifecycle | .NET 8.0 | True | 30.23 ns | 3.64 | - |
| Generated v2 — full lifecycle | .NET 8.0 | True | 31.43 ns | 3.78 | - |
| Manual: LoggerMessage.Define — single Info | .NET 9.0 | True | 6.83 ns | 1.00 | - |
| Generated v1 — single Info | .NET 9.0 | True | 7.58 ns | 1.11 | - |
| Generated v2 — single Info | .NET 9.0 | True | 7.37 ns | 1.08 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 9.0 | True | 26.08 ns | 3.82 | - |
| Generated v1 — full lifecycle | .NET 9.0 | True | 27.35 ns | 4.01 | - |
| Generated v2 — full lifecycle | .NET 9.0 | True | 27.34 ns | 4.01 | - |
| Manual: LoggerMessage.Define — single Info | .NET FW 4.7 | True | 17.61 ns | 1.00 | - |
| Generated v1 — single Info | .NET FW 4.7 | True | 16.61 ns | 0.94 | - |
| Generated v2 — single Info | .NET FW 4.7 | True | 17.75 ns | 1.01 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET FW 4.7 | True | 69.99 ns | 3.98 | - |
| Generated v1 — full lifecycle | .NET FW 4.7 | True | 70.23 ns | 3.99 | - |
| Generated v2 — full lifecycle | .NET FW 4.7 | True | 71.38 ns | 4.06 | - |
| Manual: LoggerMessage.Define — single Info | .NET FW 4.8 | True | 17.23 ns | 1.00 | - |
| Generated v1 — single Info | .NET FW 4.8 | True | 17.14 ns | 0.99 | - |
| Generated v2 — single Info | .NET FW 4.8 | True | 16.98 ns | 0.99 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET FW 4.8 | True | 70.18 ns | 4.07 | - |
| Generated v1 — full lifecycle | .NET FW 4.8 | True | 70.43 ns | 4.09 | - |
| Generated v2 — full lifecycle | .NET FW 4.8 | True | 69.94 ns | 4.06 | - |
Interpretation: Generated v1 and v2 both allocate zero bytes across all runtimes. On .NET 10.0 with logging active, v1 (4.47 ns) and v2 (4.16 ns) are both within ~2–5% of the manual LoggerMessage.Define baseline (4.39 ns). On .NET 8.0 and later, generated code is indistinguishable from hand-written code. On .NET Framework, all three approaches converge to the same ~70 ns for a 4-call lifecycle.
Source: LoggerMultiTargetBenchmarks
Compares single-target logging-only vs. multi-target (Activity + Logging + Metrics) generated code, alongside a hand-written multi-target baseline.
| Method | Runtime | HasListener | Mean | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|
| Multi-target (manual): start + complete | .NET 10.0 | False | 11.27 ns | 1.00 | 24 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 10.0 | False | 11.03 ns | 0.98 | 24 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 10.0 | False | 11.64 ns | 1.03 | 24 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 10.0 | False | 12.48 ns | 1.11 | 24 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 10.0 | False | 12.10 ns | 1.07 | 24 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 10.0 | False | 12.99 ns | 1.15 | 24 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 10.0 | False | 17.96 ns | 1.59 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 10.0 | False | 17.80 ns | 1.58 | - | 0.00 |
| Multi-target (manual): start + complete | .NET 8.0 | False | 18.09 ns | 1.00 | 24 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 8.0 | False | 18.87 ns | 1.04 | 24 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 8.0 | False | 19.10 ns | 1.06 | 24 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 8.0 | False | 18.88 ns | 1.04 | 24 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 8.0 | False | 19.43 ns | 1.07 | 24 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 8.0 | False | 19.36 ns | 1.07 | 24 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 8.0 | False | 31.61 ns | 1.75 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 8.0 | False | 32.10 ns | 1.77 | - | 0.00 |
| Multi-target (manual): start + complete | .NET 9.0 | False | 16.15 ns | 1.00 | 24 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 9.0 | False | 17.38 ns | 1.08 | 24 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 9.0 | False | 16.50 ns | 1.02 | 24 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 9.0 | False | 17.26 ns | 1.07 | 24 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 9.0 | False | 16.51 ns | 1.02 | 24 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 9.0 | False | 16.13 ns | 1.00 | 24 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 9.0 | False | 27.20 ns | 1.68 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 9.0 | False | 26.75 ns | 1.66 | - | 0.00 |
| Multi-target (manual): start + complete | .NET FW 4.7 | False | 73.46 ns | 1.00 | 24 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET FW 4.7 | False | 73.69 ns | 1.00 | 24 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET FW 4.7 | False | 75.84 ns | 1.03 | 24 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET FW 4.7 | False | 94.32 ns | 1.28 | 24 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET FW 4.7 | False | 93.37 ns | 1.27 | 24 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET FW 4.7 | False | 94.21 ns | 1.28 | 24 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET FW 4.7 | False | 72.47 ns | 0.99 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET FW 4.7 | False | 72.13 ns | 0.98 | - | 0.00 |
| Multi-target (manual): start + complete | .NET FW 4.8 | False | 72.37 ns | 1.00 | 24 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET FW 4.8 | False | 73.25 ns | 1.01 | 24 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET FW 4.8 | False | 73.74 ns | 1.02 | 24 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET FW 4.8 | False | 90.78 ns | 1.25 | 24 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET FW 4.8 | False | 98.78 ns | 1.37 | 24 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET FW 4.8 | False | 95.74 ns | 1.32 | 24 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET FW 4.8 | False | 72.89 ns | 1.01 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET FW 4.8 | False | 72.45 ns | 1.00 | - | 0.00 |
| Multi-target (manual): start + complete | .NET 10.0 | True | 237.26 ns | 1.00 | 1032 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 10.0 | True | 229.61 ns | 0.97 | 1032 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 10.0 | True | 235.24 ns | 0.99 | 1032 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 10.0 | True | 220.70 ns | 0.93 | 1032 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 10.0 | True | 229.56 ns | 0.97 | 1032 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 10.0 | True | 230.89 ns | 0.97 | 1032 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 10.0 | True | 17.83 ns | 0.08 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 10.0 | True | 17.77 ns | 0.07 | - | 0.00 |
| Multi-target (manual): start + complete | .NET 8.0 | True | 274.42 ns | 1.00 | 1032 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 8.0 | True | 274.83 ns | 1.00 | 1032 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 8.0 | True | 267.53 ns | 0.98 | 1032 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 8.0 | True | 266.80 ns | 0.97 | 1032 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 8.0 | True | 271.30 ns | 0.99 | 1032 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 8.0 | True | 271.51 ns | 0.99 | 1032 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 8.0 | True | 30.73 ns | 0.11 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 8.0 | True | 30.96 ns | 0.11 | - | 0.00 |
| Multi-target (manual): start + complete | .NET 9.0 | True | 254.49 ns | 1.00 | 1032 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 9.0 | True | 255.51 ns | 1.00 | 1032 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 9.0 | True | 242.20 ns | 0.95 | 1032 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 9.0 | True | 243.63 ns | 0.96 | 1032 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 9.0 | True | 255.47 ns | 1.00 | 1032 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 9.0 | True | 260.15 ns | 1.02 | 1032 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 9.0 | True | 28.71 ns | 0.11 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 9.0 | True | 26.95 ns | 0.11 | - | 0.00 |
| Multi-target (manual): start + complete | .NET FW 4.7 | True | 634.23 ns | 1.00 | 1292 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET FW 4.7 | True | 605.31 ns | 0.95 | 1292 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET FW 4.7 | True | 624.15 ns | 0.98 | 1292 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET FW 4.7 | True | 636.16 ns | 1.00 | 1292 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET FW 4.7 | True | 643.34 ns | 1.01 | 1292 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET FW 4.7 | True | 645.00 ns | 1.02 | 1292 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET FW 4.7 | True | 73.52 ns | 0.12 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET FW 4.7 | True | 73.02 ns | 0.12 | - | 0.00 |
| Multi-target (manual): start + complete | .NET FW 4.8 | True | 631.25 ns | 1.00 | 1292 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET FW 4.8 | True | 638.79 ns | 1.01 | 1292 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET FW 4.8 | True | 621.33 ns | 0.98 | 1292 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET FW 4.8 | True | 665.51 ns | 1.05 | 1292 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET FW 4.8 | True | 656.73 ns | 1.04 | 1292 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET FW 4.8 | True | 649.40 ns | 1.03 | 1292 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET FW 4.8 | True | 72.64 ns | 0.12 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET FW 4.8 | True | 72.41 ns | 0.11 | - | 0.00 |
Interpretation: Generated v1 and v2 both allocate identically to hand-written multi-target code (24 B no-listener, 1032 B with-listener active). When a listener is active, all three multi-target implementations are within ~3% of each other. The Activity-creation cost dominates when a listener is active — single-target logging-only (~18 ns) accounts for only a small fraction of the total multi-target cost (~230 ns).
Source: MultiTargetVsSingleTargetBenchmarks
Measures the overhead of emitting Activity + Logging + Metrics from a single method call (multi-target) vs. Activity-only (single-target), comparing generated and manual code.
| Method | Runtime | HasListener | Mean | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|
| Single-target (generated): start + complete | .NET 10.0 | False | 0.54 ns | 1.00 | - | NA |
| Multi-target (generated): start + complete | .NET 10.0 | False | 12.44 ns | 22.91 | 24 B | NA |
| Multi-target (manual): start + complete | .NET 10.0 | False | 12.28 ns | 22.61 | 24 B | NA |
| Multi-target (generated): start + complete + record latency | .NET 10.0 | False | 12.23 ns | 22.51 | 24 B | NA |
| Multi-target (manual): start + complete + record latency | .NET 10.0 | False | 12.20 ns | 22.46 | 24 B | NA |
| Single-target (generated): start + complete | .NET 8.0 | False | 0.74 ns | 1.00 | - | NA |
| Multi-target (generated): start + complete | .NET 8.0 | False | 18.40 ns | 24.75 | 24 B | NA |
| Multi-target (manual): start + complete | .NET 8.0 | False | 18.19 ns | 24.48 | 24 B | NA |
| Multi-target (generated): start + complete + record latency | .NET 8.0 | False | 19.72 ns | 26.54 | 24 B | NA |
| Multi-target (manual): start + complete + record latency | .NET 8.0 | False | 18.47 ns | 24.85 | 24 B | NA |
| Single-target (generated): start + complete | .NET 9.0 | False | 0.50 ns | 1.00 | - | NA |
| Multi-target (generated): start + complete | .NET 9.0 | False | 17.41 ns | 35.10 | 24 B | NA |
| Multi-target (manual): start + complete | .NET 9.0 | False | 16.23 ns | 32.70 | 24 B | NA |
| Multi-target (generated): start + complete + record latency | .NET 9.0 | False | 16.19 ns | 32.63 | 24 B | NA |
| Multi-target (manual): start + complete + record latency | .NET 9.0 | False | 16.19 ns | 32.63 | 24 B | NA |
| Single-target (generated): start + complete | .NET FW 4.7 | False | 16.04 ns | 1.00 | - | NA |
| Multi-target (generated): start + complete | .NET FW 4.7 | False | 73.73 ns | 4.60 | 24 B | NA |
| Multi-target (manual): start + complete | .NET FW 4.7 | False | 70.87 ns | 4.42 | 24 B | NA |
| Multi-target (generated): start + complete + record latency | .NET FW 4.7 | False | 91.70 ns | 5.72 | 24 B | NA |
| Multi-target (manual): start + complete + record latency | .NET FW 4.7 | False | 91.40 ns | 5.70 | 24 B | NA |
| Single-target (generated): start + complete | .NET FW 4.8 | False | 15.83 ns | 1.00 | - | NA |
| Multi-target (generated): start + complete | .NET FW 4.8 | False | 73.44 ns | 4.64 | 24 B | NA |
| Multi-target (manual): start + complete | .NET FW 4.8 | False | 71.39 ns | 4.51 | 24 B | NA |
| Multi-target (generated): start + complete + record latency | .NET FW 4.8 | False | 93.12 ns | 5.88 | 24 B | NA |
| Multi-target (manual): start + complete + record latency | .NET FW 4.8 | False | 90.71 ns | 5.73 | 24 B | NA |
| Single-target (generated): start + complete | .NET 10.0 | True | 218.75 ns | 1.00 | 1008 B | 1.00 |
| Multi-target (generated): start + complete | .NET 10.0 | True | 233.61 ns | 1.07 | 1032 B | 1.02 |
| Multi-target (manual): start + complete | .NET 10.0 | True | 229.27 ns | 1.05 | 1032 B | 1.02 |
| Multi-target (generated): start + complete + record latency | .NET 10.0 | True | 219.61 ns | 1.01 | 1032 B | 1.02 |
| Multi-target (manual): start + complete + record latency | .NET 10.0 | True | 220.02 ns | 1.01 | 1032 B | 1.02 |
| Single-target (generated): start + complete | .NET 8.0 | True | 245.63 ns | 1.00 | 1008 B | 1.00 |
| Multi-target (generated): start + complete | .NET 8.0 | True | 254.75 ns | 1.04 | 1032 B | 1.02 |
| Multi-target (manual): start + complete | .NET 8.0 | True | 270.80 ns | 1.10 | 1032 B | 1.02 |
| Multi-target (generated): start + complete + record latency | .NET 8.0 | True | 267.03 ns | 1.09 | 1032 B | 1.02 |
| Multi-target (manual): start + complete + record latency | .NET 8.0 | True | 282.41 ns | 1.15 | 1032 B | 1.02 |
| Single-target (generated): start + complete | .NET 9.0 | True | 226.77 ns | 1.00 | 1008 B | 1.00 |
| Multi-target (generated): start + complete | .NET 9.0 | True | 272.37 ns | 1.20 | 1032 B | 1.02 |
| Multi-target (manual): start + complete | .NET 9.0 | True | 251.63 ns | 1.11 | 1032 B | 1.02 |
| Multi-target (generated): start + complete + record latency | .NET 9.0 | True | 251.54 ns | 1.11 | 1032 B | 1.02 |
| Multi-target (manual): start + complete + record latency | .NET 9.0 | True | 231.85 ns | 1.02 | 1032 B | 1.02 |
| Single-target (generated): start + complete | .NET FW 4.7 | True | 523.77 ns | 1.00 | 1268 B | 1.00 |
| Multi-target (generated): start + complete | .NET FW 4.7 | True | 620.08 ns | 1.18 | 1292 B | 1.02 |
| Multi-target (manual): start + complete | .NET FW 4.7 | True | 635.64 ns | 1.21 | 1292 B | 1.02 |
| Multi-target (generated): start + complete + record latency | .NET FW 4.7 | True | 653.98 ns | 1.25 | 1292 B | 1.02 |
| Multi-target (manual): start + complete + record latency | .NET FW 4.7 | True | 639.39 ns | 1.22 | 1292 B | 1.02 |
| Single-target (generated): start + complete | .NET FW 4.8 | True | 535.07 ns | 1.00 | 1268 B | 1.00 |
| Multi-target (generated): start + complete | .NET FW 4.8 | True | 606.54 ns | 1.13 | 1292 B | 1.02 |
| Multi-target (manual): start + complete | .NET FW 4.8 | True | 631.65 ns | 1.18 | 1292 B | 1.02 |
| Multi-target (generated): start + complete + record latency | .NET FW 4.8 | True | 626.60 ns | 1.17 | 1292 B | 1.02 |
| Multi-target (manual): start + complete + record latency | .NET FW 4.8 | True | 649.22 ns | 1.21 | 1292 B | 1.02 |
Interpretation: When an Activity listener is active (production path), multi-target generation adds ~7% overhead over single-target Activity-only on .NET 10.0, reflecting the real cost of the extra log call and metric increment — not generated-code overhead. The generated multi-target code matches hand-written multi-target code closely across all runtimes.
Source: TagListBenchmarks
Demonstrates the source generator's tag-count optimization: methods with fewer than 4 tags pass them directly as inline KeyValuePair parameters (no heap allocation), while methods with 4 or more tags use a stack-allocated TagList struct to batch them.
| Method | Runtime | Mean | Ratio | Allocated |
|---|---|---|---|---|
| 0 tags: histogram record | .NET 10.0 | 0.362 ns | 1.00 | - |
| 1 tag: auto-counter add | .NET 10.0 | 0.353 ns | 0.97 | - |
| 3 tags: histogram record | .NET 10.0 | 0.735 ns | 2.03 | - |
| 4 tags (TagList): auto-counter add | .NET 10.0 | 4.100 ns | 11.33 | - |
| 5 tags (TagList): auto-counter add | .NET 10.0 | 5.289 ns | 14.62 | - |
| 6 tags (TagList): histogram record | .NET 10.0 | 5.938 ns | 16.41 | - |
| 0 tags: histogram record | .NET 8.0 | 0.541 ns | 1.00 | - |
| 1 tag: auto-counter add | .NET 8.0 | 0.357 ns | 0.66 | - |
| 3 tags: histogram record | .NET 8.0 | 0.720 ns | 1.33 | - |
| 4 tags (TagList): auto-counter add | .NET 8.0 | 3.685 ns | 6.82 | - |
| 5 tags (TagList): auto-counter add | .NET 8.0 | 4.416 ns | 8.17 | - |
| 6 tags (TagList): histogram record | .NET 8.0 | 4.353 ns | 8.06 | - |
| 0 tags: histogram record | .NET 9.0 | 0.167 ns | 1.01 | - |
| 1 tag: auto-counter add | .NET 9.0 | 0.370 ns | 2.22 | - |
| 3 tags: histogram record | .NET 9.0 | 0.498 ns | 2.99 | - |
| 4 tags (TagList): auto-counter add | .NET 9.0 | 3.614 ns | 21.72 | - |
| 5 tags (TagList): auto-counter add | .NET 9.0 | 4.037 ns | 24.26 | - |
| 6 tags (TagList): histogram record | .NET 9.0 | 3.994 ns | 24.00 | - |
| 0 tags: histogram record | .NET FW 4.7 | 15.175 ns | 1.00 | - |
| 1 tag: auto-counter add | .NET FW 4.7 | 18.781 ns | 1.24 | - |
| 3 tags: histogram record | .NET FW 4.7 | 29.760 ns | 1.96 | - |
| 4 tags (TagList): auto-counter add | .NET FW 4.7 | 52.394 ns | 3.45 | - |
| 5 tags (TagList): auto-counter add | .NET FW 4.7 | 60.056 ns | 3.96 | - |
| 6 tags (TagList): histogram record | .NET FW 4.7 | 58.220 ns | 3.84 | - |
| 0 tags: histogram record | .NET FW 4.8 | 15.347 ns | 1.00 | - |
| 1 tag: auto-counter add | .NET FW 4.8 | 18.786 ns | 1.22 | - |
| 3 tags: histogram record | .NET FW 4.8 | 29.358 ns | 1.91 | - |
| 4 tags (TagList): auto-counter add | .NET FW 4.8 | 49.587 ns | 3.23 | - |
| 5 tags (TagList): auto-counter add | .NET FW 4.8 | 60.599 ns | 3.95 | - |
| 6 tags (TagList): histogram record | .NET FW 4.8 | 59.192 ns | 3.86 | - |
Interpretation: All metrics recording is allocation-free regardless of tag count. The TagList path (≥4 tags) costs 11–17× more in CPU time than the inline path on .NET 10.0; both remain in single-digit-nanosecond range.
Source: MetricsBenchmarks
Compares the source-generator-produced implementation against hand-written raw .NET metrics API calls for Counter, UpDownCounter, and Histogram.
Note: On .NET 10.0, the JIT can eliminate unobserved metric calls (no active
MeterListener) almost entirely, reducing manual baselines to sub-picosecond noise. Because these near-zero baselines are statistically unreliable, BenchmarkDotNet reports?for all.NET 10.0ratios in this section. The absolute nanosecond values remain meaningful — generated instruments cost ~0.17–0.40 ns each regardless of runtime. On .NET 8.0, .NET 9.0, and .NET Framework, all calls are in the same sub-nanosecond to ~20 ns range with no allocations.
| Method | Runtime | Mean | Ratio | Allocated |
|---|---|---|---|---|
| Manual: auto-counter (0 tags) | .NET 10.0 | 0.016 ns | ? | - |
| Generated: auto-counter (0 tags) | .NET 10.0 | 0.369 ns | ? | - |
| Manual: auto-counter (1 tag) | .NET 10.0 | 0.213 ns | ? | - |
| Generated: auto-counter (1 tag) | .NET 10.0 | 0.363 ns | ? | - |
| Manual: up-down counter | .NET 10.0 | 0.002 ns | ? | - |
| Generated: up-down counter | .NET 10.0 | 0.401 ns | ? | - |
| Manual: histogram (0 tags) | .NET 10.0 | 0.000 ns | ? | - |
| Generated: histogram (0 tags) | .NET 10.0 | 0.372 ns | ? | - |
| Manual: histogram (1 tag) | .NET 10.0 | 0.184 ns | ? | - |
| Generated: histogram (1 tag) | .NET 10.0 | 0.367 ns | ? | - |
| Manual: auto-counter (0 tags) | .NET 8.0 | 0.487 ns | 1.02 | - |
| Generated: auto-counter (0 tags) | .NET 8.0 | 0.391 ns | 0.82 | - |
| Manual: auto-counter (1 tag) | .NET 8.0 | 0.344 ns | 0.72 | - |
| Generated: auto-counter (1 tag) | .NET 8.0 | 0.380 ns | 0.79 | - |
| Manual: up-down counter | .NET 8.0 | 0.415 ns | 0.87 | - |
| Generated: up-down counter | .NET 8.0 | 0.551 ns | 1.15 | - |
| Manual: histogram (0 tags) | .NET 8.0 | 0.361 ns | 0.75 | - |
| Generated: histogram (0 tags) | .NET 8.0 | 0.327 ns | 0.68 | - |
| Manual: histogram (1 tag) | .NET 8.0 | 0.366 ns | 0.76 | - |
| Generated: histogram (1 tag) | .NET 8.0 | 0.598 ns | 1.25 | - |
| Manual: auto-counter (0 tags) | .NET 9.0 | 0.429 ns | 1.01 | - |
| Generated: auto-counter (0 tags) | .NET 9.0 | 0.183 ns | 0.43 | - |
| Manual: auto-counter (1 tag) | .NET 9.0 | 0.385 ns | 0.90 | - |
| Generated: auto-counter (1 tag) | .NET 9.0 | 0.394 ns | 0.92 | - |
| Manual: up-down counter | .NET 9.0 | 0.339 ns | 0.79 | - |
| Generated: up-down counter | .NET 9.0 | 0.195 ns | 0.46 | - |
| Manual: histogram (0 tags) | .NET 9.0 | 0.189 ns | 0.44 | - |
| Generated: histogram (0 tags) | .NET 9.0 | 0.171 ns | 0.40 | - |
| Manual: histogram (1 tag) | .NET 9.0 | 0.377 ns | 0.88 | - |
| Generated: histogram (1 tag) | .NET 9.0 | 0.188 ns | 0.44 | - |
| Manual: auto-counter (0 tags) | .NET FW 4.7 | 20.66 ns | 1.00 | - |
| Generated: auto-counter (0 tags) | .NET FW 4.7 | 15.63 ns | 0.76 | - |
| Manual: auto-counter (1 tag) | .NET FW 4.7 | 19.27 ns | 0.93 | - |
| Generated: auto-counter (1 tag) | .NET FW 4.7 | 18.66 ns | 0.90 | - |
| Manual: up-down counter | .NET FW 4.7 | 19.15 ns | 0.93 | - |
| Generated: up-down counter | .NET FW 4.7 | 16.11 ns | 0.78 | - |
| Manual: histogram (0 tags) | .NET FW 4.7 | 15.45 ns | 0.75 | - |
| Generated: histogram (0 tags) | .NET FW 4.7 | 16.15 ns | 0.78 | - |
| Manual: histogram (1 tag) | .NET FW 4.7 | 19.08 ns | 0.92 | - |
| Generated: histogram (1 tag) | .NET FW 4.7 | 19.70 ns | 0.95 | - |
| Manual: auto-counter (0 tags) | .NET FW 4.8 | 15.55 ns | 1.00 | - |
| Generated: auto-counter (0 tags) | .NET FW 4.8 | 15.42 ns | 0.99 | - |
| Manual: auto-counter (1 tag) | .NET FW 4.8 | 18.32 ns | 1.18 | - |
| Generated: auto-counter (1 tag) | .NET FW 4.8 | 18.28 ns | 1.18 | - |
| Manual: up-down counter | .NET FW 4.8 | 15.16 ns | 0.98 | - |
| Generated: up-down counter | .NET FW 4.8 | 15.55 ns | 1.00 | - |
| Manual: histogram (0 tags) | .NET FW 4.8 | 14.90 ns | 0.96 | - |
| Generated: histogram (0 tags) | .NET FW 4.8 | 15.61 ns | 1.00 | - |
| Manual: histogram (1 tag) | .NET FW 4.8 | 18.64 ns | 1.20 | - |
| Generated: histogram (1 tag) | .NET FW 4.8 | 19.45 ns | 1.25 | - |
Interpretation: On .NET 10.0, generated instruments measure ~0.17–0.40 ns per call — the manual baselines are near-zero noise because the JIT eliminates unlistened calls, so no meaningful ratio can be computed. On .NET 8.0/9.0 and .NET Framework, generated and manual instruments are within ±25% of each other, with 0 allocations across all runtimes and instrument types.
ObservableCounter, ObservableGauge, and ObservableUpDownCounter are not benchmarked because they have no per-operation hot path to compare.
These instruments register a callback once at construction time (via meter.CreateObservableCounter(name, callback)) and are polled by the metrics collection pipeline. The source generator produces the identical CreateObservable* call that you would write by hand — there is no wrapper layer on the measurement path. Benchmarking the one-time registration call would not reflect production performance.
Full CSV and HTML benchmark artifacts are available in BenchmarkDotNet.Artifacts/results/ in the repository.
Important
Consider helping children around the world affected by conflict. You can donate any amount to War Child here - any amount can help save a life.
Purview Telemetry Source Generator v4.0.0-prerelease.1 | Home | Getting Started | FAQ | Breaking Changes | GitHub