Skip to content

Performance

Kieron Lanning edited this page Mar 16, 2026 · 2 revisions

Performance Benchmarks

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.0

Results 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.


Environment

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

Activities

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.


Logging

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.


Logger Multi-Target

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).


Multi-Target vs. Single-Target

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.


Metrics — TagList Threshold

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.


Metrics — Generated vs. Manual

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.0 ratios 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.


Observable Instruments

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.


Raw Results

Full CSV and HTML benchmark artifacts are available in BenchmarkDotNet.Artifacts/results/ in the repository.

Clone this wiki locally