-
Notifications
You must be signed in to change notification settings - Fork 3
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.8117/25H2/2025Update/HudsonValley2)
13th Gen Intel Core i9-13900KF 3.00GHz, 1 CPU, 32 logical and 24 physical cores
.NET SDK 10.0.201
[Host] : .NET 10.0.5 (10.0.5, 10.0.526.15411), X64 RyuJIT x86-64-v3
.NET 10.0 : .NET 10.0.5 (10.0.5, 10.0.526.15411), 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 9.0 : .NET 9.0.14 (9.0.14, 9.0.1426.11910), X64 RyuJIT x86-64-v3
Note: .NET Framework 4.7/4.8 targets did not produce results in this run and are excluded from the tables below.
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.56 ns | 1.00 | - | NA |
| Generated: start + complete | .NET 10.0 | False | 0.55 ns | 0.99 | - | NA |
| Manual: start + fail | .NET 10.0 | False | 0.72 ns | 1.29 | - | NA |
| Generated: start + fail | .NET 10.0 | False | 0.52 ns | 0.93 | - | NA |
| Manual: start + complete | .NET 8.0 | False | 0.71 ns | 1.00 | - | NA |
| Generated: start + complete | .NET 8.0 | False | 0.71 ns | 1.01 | - | NA |
| Manual: start + fail | .NET 8.0 | False | 0.91 ns | 1.30 | - | NA |
| Generated: start + fail | .NET 8.0 | False | 0.90 ns | 1.28 | - | NA |
| Manual: start + complete | .NET 9.0 | False | 0.54 ns | 1.00 | - | NA |
| Generated: start + complete | .NET 9.0 | False | 0.55 ns | 1.01 | - | NA |
| Manual: start + fail | .NET 9.0 | False | 0.73 ns | 1.36 | - | NA |
| Generated: start + fail | .NET 9.0 | False | 0.70 ns | 1.30 | - | NA |
| Manual: start + complete | .NET 10.0 | True | 217.75 ns | 1.00 | 1008 B | 1.00 |
| Generated: start + complete | .NET 10.0 | True | 204.03 ns | 0.94 | 1008 B | 1.00 |
| Manual: start + fail | .NET 10.0 | True | 198.43 ns | 0.91 | 920 B | 0.91 |
| Generated: start + fail | .NET 10.0 | True | 189.26 ns | 0.87 | 920 B | 0.91 |
| Manual: start + complete | .NET 8.0 | True | 241.11 ns | 1.00 | 1008 B | 1.00 |
| Generated: start + complete | .NET 8.0 | True | 250.49 ns | 1.04 | 1008 B | 1.00 |
| Manual: start + fail | .NET 8.0 | True | 223.87 ns | 0.93 | 920 B | 0.91 |
| Generated: start + fail | .NET 8.0 | True | 222.14 ns | 0.92 | 920 B | 0.91 |
| Manual: start + complete | .NET 9.0 | True | 216.84 ns | 1.00 | 1008 B | 1.00 |
| Generated: start + complete | .NET 9.0 | True | 214.30 ns | 0.99 | 1008 B | 1.00 |
| Manual: start + fail | .NET 9.0 | True | 200.43 ns | 0.92 | 920 B | 0.91 |
| Generated: start + fail | .NET 9.0 | True | 222.14 ns | 1.03 | 920 B | 0.91 |
Interpretation: Generated activities match or outperform hand-written code and allocate identically across all tested 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.21 ns | 1.01 | - |
| Generated v1 — single Info | .NET 10.0 | False | 0.18 ns | 0.89 | - |
| Generated v2 — single Info | .NET 10.0 | False | 0.21 ns | 1.00 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 10.0 | False | 0.37 ns | 1.80 | - |
| Generated v1 — full lifecycle | .NET 10.0 | False | 0.75 ns | 3.63 | - |
| Generated v2 — full lifecycle | .NET 10.0 | False | 0.76 ns | 3.71 | - |
| Manual: LoggerMessage.Define — single Info | .NET 8.0 | False | 0.18 ns | 1.00 | - |
| Generated v1 — single Info | .NET 8.0 | False | 0.39 ns | 2.15 | - |
| Generated v2 — single Info | .NET 8.0 | False | 0.37 ns | 2.04 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 8.0 | False | 0.92 ns | 5.12 | - |
| Generated v1 — full lifecycle | .NET 8.0 | False | 1.28 ns | 7.13 | - |
| Generated v2 — full lifecycle | .NET 8.0 | False | 1.34 ns | 7.44 | - |
| Manual: LoggerMessage.Define — single Info | .NET 9.0 | False | 0.18 ns | 1.00 | - |
| Generated v1 — single Info | .NET 9.0 | False | 0.18 ns | 0.99 | - |
| Generated v2 — single Info | .NET 9.0 | False | 0.17 ns | 0.91 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 9.0 | False | 0.54 ns | 2.99 | - |
| Generated v1 — full lifecycle | .NET 9.0 | False | 1.49 ns | 8.23 | - |
| Generated v2 — full lifecycle | .NET 9.0 | False | 1.47 ns | 8.08 | - |
| Manual: LoggerMessage.Define — single Info | .NET 10.0 | True | 4.29 ns | 1.00 | - |
| Generated v1 — single Info | .NET 10.0 | True | 4.24 ns | 0.99 | - |
| Generated v2 — single Info | .NET 10.0 | True | 4.20 ns | 0.98 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 10.0 | True | 17.73 ns | 4.13 | - |
| Generated v1 — full lifecycle | .NET 10.0 | True | 19.52 ns | 4.55 | - |
| Generated v2 — full lifecycle | .NET 10.0 | True | 18.81 ns | 4.38 | - |
| Manual: LoggerMessage.Define — single Info | .NET 8.0 | True | 7.57 ns | 1.00 | - |
| Generated v1 — single Info | .NET 8.0 | True | 7.34 ns | 0.97 | - |
| Generated v2 — single Info | .NET 8.0 | True | 7.26 ns | 0.96 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 8.0 | True | 28.73 ns | 3.79 | - |
| Generated v1 — full lifecycle | .NET 8.0 | True | 29.90 ns | 3.95 | - |
| Generated v2 — full lifecycle | .NET 8.0 | True | 29.85 ns | 3.94 | - |
| Manual: LoggerMessage.Define — single Info | .NET 9.0 | True | 6.10 ns | 1.00 | - |
| Generated v1 — single Info | .NET 9.0 | True | 6.22 ns | 1.02 | - |
| Generated v2 — single Info | .NET 9.0 | True | 6.25 ns | 1.03 | - |
| Manual: LoggerMessage.Define — full lifecycle | .NET 9.0 | True | 23.88 ns | 3.92 | - |
| Generated v1 — full lifecycle | .NET 9.0 | True | 24.55 ns | 4.03 | - |
| Generated v2 — full lifecycle | .NET 9.0 | True | 24.75 ns | 4.06 | - |
Interpretation: Generated v1 and v2 both allocate zero bytes across all runtimes. On .NET 10.0 with logging active, v1 (4.24 ns) and v2 (4.20 ns) are within ~1-2% of the manual LoggerMessage.Define baseline (4.29 ns). On .NET 8.0 and .NET 9.0, generated code is indistinguishable from hand-written code.
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.87 ns | 1.00 | 24 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 10.0 | False | 27.85 ns | 2.35 | 24 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 10.0 | False | 26.67 ns | 2.25 | 24 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 10.0 | False | 23.67 ns | 1.99 | 24 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 10.0 | False | 18.29 ns | 1.54 | 24 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 10.0 | False | 12.27 ns | 1.03 | 24 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 10.0 | False | 17.84 ns | 1.50 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 10.0 | False | 17.66 ns | 1.49 | - | 0.00 |
| Multi-target (manual): start + complete | .NET 8.0 | False | 17.55 ns | 1.00 | 24 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 8.0 | False | 18.64 ns | 1.06 | 24 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 8.0 | False | 18.81 ns | 1.07 | 24 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 8.0 | False | 15.90 ns | 0.91 | 24 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 8.0 | False | 17.38 ns | 0.99 | 24 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 8.0 | False | 16.92 ns | 0.96 | 24 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 8.0 | False | 31.09 ns | 1.77 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 8.0 | False | 31.25 ns | 1.78 | - | 0.00 |
| Multi-target (manual): start + complete | .NET 9.0 | False | 14.51 ns | 1.00 | 24 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 9.0 | False | 14.67 ns | 1.01 | 24 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 9.0 | False | 14.60 ns | 1.01 | 24 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 9.0 | False | 14.34 ns | 0.99 | 24 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 9.0 | False | 14.66 ns | 1.01 | 24 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 9.0 | False | 14.34 ns | 0.99 | 24 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 9.0 | False | 24.22 ns | 1.67 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 9.0 | False | 24.32 ns | 1.68 | - | 0.00 |
| Multi-target (manual): start + complete | .NET 10.0 | True | 243.70 ns | 1.00 | 1032 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 10.0 | True | 226.39 ns | 0.93 | 1032 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 10.0 | True | 228.01 ns | 0.94 | 1032 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 10.0 | True | 219.11 ns | 0.90 | 1032 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 10.0 | True | 220.89 ns | 0.91 | 1032 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 10.0 | True | 220.03 ns | 0.90 | 1032 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 10.0 | True | 19.27 ns | 0.08 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 10.0 | True | 17.73 ns | 0.07 | - | 0.00 |
| Multi-target (manual): start + complete | .NET 8.0 | True | 259.01 ns | 1.00 | 1032 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 8.0 | True | 265.32 ns | 1.02 | 1032 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 8.0 | True | 263.96 ns | 1.02 | 1032 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 8.0 | True | 252.01 ns | 0.97 | 1032 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 8.0 | True | 261.70 ns | 1.01 | 1032 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 8.0 | True | 252.52 ns | 0.98 | 1032 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 8.0 | True | 31.17 ns | 0.12 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 8.0 | True | 30.51 ns | 0.12 | - | 0.00 |
| Multi-target (manual): start + complete | .NET 9.0 | True | 238.41 ns | 1.00 | 1032 B | 1.00 |
| Multi-target (generated v1): start + complete | .NET 9.0 | True | 243.77 ns | 1.02 | 1032 B | 1.00 |
| Multi-target (generated v2): start + complete | .NET 9.0 | True | 240.78 ns | 1.01 | 1032 B | 1.00 |
| Multi-target (manual): full lifecycle | .NET 9.0 | True | 234.18 ns | 0.98 | 1032 B | 1.00 |
| Multi-target (generated v1): full lifecycle | .NET 9.0 | True | 233.81 ns | 0.98 | 1032 B | 1.00 |
| Multi-target (generated v2): full lifecycle | .NET 9.0 | True | 243.42 ns | 1.02 | 1032 B | 1.00 |
| Single-target (generated v1): full lifecycle | .NET 9.0 | True | 27.69 ns | 0.12 | - | 0.00 |
| Single-target (generated v2): full lifecycle | .NET 9.0 | True | 26.07 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 ~7% of each other on .NET 10.0. 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.55 ns | 1.00 | - | NA |
| Multi-target (generated): start + complete | .NET 10.0 | False | 11.46 ns | 20.88 | 24 B | NA |
| Multi-target (manual): start + complete | .NET 10.0 | False | 11.76 ns | 21.43 | 24 B | NA |
| Multi-target (generated): start + complete + record latency | .NET 10.0 | False | 12.16 ns | 22.15 | 24 B | NA |
| Multi-target (manual): start + complete + record latency | .NET 10.0 | False | 11.83 ns | 21.55 | 24 B | NA |
| Single-target (generated): start + complete | .NET 8.0 | False | 0.73 ns | 1.00 | - | NA |
| Multi-target (generated): start + complete | .NET 8.0 | False | 16.07 ns | 21.92 | 24 B | NA |
| Multi-target (manual): start + complete | .NET 8.0 | False | 16.45 ns | 22.43 | 24 B | NA |
| Multi-target (generated): start + complete + record latency | .NET 8.0 | False | 16.90 ns | 23.05 | 24 B | NA |
| Multi-target (manual): start + complete + record latency | .NET 8.0 | False | 15.48 ns | 21.11 | 24 B | NA |
| Single-target (generated): start + complete | .NET 9.0 | False | 0.55 ns | 1.00 | - | NA |
| Multi-target (generated): start + complete | .NET 9.0 | False | 14.31 ns | 26.13 | 24 B | NA |
| Multi-target (manual): start + complete | .NET 9.0 | False | 13.96 ns | 25.50 | 24 B | NA |
| Multi-target (generated): start + complete + record latency | .NET 9.0 | False | 15.13 ns | 27.63 | 24 B | NA |
| Multi-target (manual): start + complete + record latency | .NET 9.0 | False | 15.00 ns | 27.39 | 24 B | NA |
| Single-target (generated): start + complete | .NET 10.0 | True | 203.33 ns | 1.00 | 1008 B | 1.00 |
| Multi-target (generated): start + complete | .NET 10.0 | True | 229.91 ns | 1.13 | 1032 B | 1.02 |
| Multi-target (manual): start + complete | .NET 10.0 | True | 233.48 ns | 1.15 | 1032 B | 1.02 |
| Multi-target (generated): start + complete + record latency | .NET 10.0 | True | 224.27 ns | 1.10 | 1032 B | 1.02 |
| Multi-target (manual): start + complete + record latency | .NET 10.0 | True | 217.24 ns | 1.07 | 1032 B | 1.02 |
| Single-target (generated): start + complete | .NET 8.0 | True | 242.29 ns | 1.00 | 1008 B | 1.00 |
| Multi-target (generated): start + complete | .NET 8.0 | True | 257.99 ns | 1.06 | 1032 B | 1.02 |
| Multi-target (manual): start + complete | .NET 8.0 | True | 254.46 ns | 1.05 | 1032 B | 1.02 |
| Multi-target (generated): start + complete + record latency | .NET 8.0 | True | 253.35 ns | 1.05 | 1032 B | 1.02 |
| Multi-target (manual): start + complete + record latency | .NET 8.0 | True | 261.46 ns | 1.08 | 1032 B | 1.02 |
| Single-target (generated): start + complete | .NET 9.0 | True | 219.20 ns | 1.00 | 1008 B | 1.00 |
| Multi-target (generated): start + complete | .NET 9.0 | True | 237.42 ns | 1.08 | 1032 B | 1.02 |
| Multi-target (manual): start + complete | .NET 9.0 | True | 242.88 ns | 1.11 | 1032 B | 1.02 |
| Multi-target (generated): start + complete + record latency | .NET 9.0 | True | 235.98 ns | 1.08 | 1032 B | 1.02 |
| Multi-target (manual): start + complete + record latency | .NET 9.0 | True | 221.28 ns | 1.01 | 1032 B | 1.02 |
Interpretation: When an Activity listener is active (production path), multi-target generation adds ~13% 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.34 ns | 1.00 | - |
| 1 tag: auto-counter add | .NET 10.0 | 0.37 ns | 1.09 | - |
| 3 tags: histogram record | .NET 10.0 | 0.87 ns | 2.53 | - |
| 4 tags (TagList): auto-counter add | .NET 10.0 | 4.19 ns | 12.22 | - |
| 5 tags (TagList): auto-counter add | .NET 10.0 | 5.18 ns | 15.08 | - |
| 6 tags (TagList): histogram record | .NET 10.0 | 6.51 ns | 18.97 | - |
| 0 tags: histogram record | .NET 8.0 | 0.54 ns | 1.00 | - |
| 1 tag: auto-counter add | .NET 8.0 | 0.37 ns | 0.68 | - |
| 3 tags: histogram record | .NET 8.0 | 0.73 ns | 1.34 | - |
| 4 tags (TagList): auto-counter add | .NET 8.0 | 3.78 ns | 6.94 | - |
| 5 tags (TagList): auto-counter add | .NET 8.0 | 4.19 ns | 7.70 | - |
| 6 tags (TagList): histogram record | .NET 8.0 | 4.17 ns | 7.67 | - |
| 0 tags: histogram record | .NET 9.0 | 0.19 ns | 1.00 | - |
| 1 tag: auto-counter add | .NET 9.0 | 0.36 ns | 1.87 | - |
| 3 tags: histogram record | .NET 9.0 | 0.68 ns | 3.55 | - |
| 4 tags (TagList): auto-counter add | .NET 9.0 | 3.62 ns | 18.93 | - |
| 5 tags (TagList): auto-counter add | .NET 9.0 | 3.78 ns | 19.76 | - |
| 6 tags (TagList): histogram record | .NET 9.0 | 3.88 ns | 20.27 | - |
Interpretation: All metrics recording is allocation-free regardless of tag count. The TagList path (>=4 tags) costs 12-19x 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.35–0.37 ns each. On .NET 8.0 and .NET 9.0, all calls are in the same sub-nanosecond range with no allocations.
| Method | Runtime | Mean | Ratio | Allocated |
|---|---|---|---|---|
| Manual: auto-counter (0 tags) | .NET 10.0 | 0.006 ns | ? | - |
| Generated: auto-counter (0 tags) | .NET 10.0 | 0.37 ns | ? | - |
| Manual: auto-counter (1 tag) | .NET 10.0 | 0.17 ns | ? | - |
| Generated: auto-counter (1 tag) | .NET 10.0 | 0.37 ns | ? | - |
| Manual: up-down counter | .NET 10.0 | 0.003 ns | ? | - |
| Generated: up-down counter | .NET 10.0 | 0.35 ns | ? | - |
| Manual: histogram (0 tags) | .NET 10.0 | 0.006 ns | ? | - |
| Generated: histogram (0 tags) | .NET 10.0 | 0.36 ns | ? | - |
| Manual: histogram (1 tag) | .NET 10.0 | 0.17 ns | ? | - |
| Generated: histogram (1 tag) | .NET 10.0 | 0.36 ns | ? | - |
| Manual: auto-counter (0 tags) | .NET 8.0 | 0.37 ns | 1.00 | - |
| Generated: auto-counter (0 tags) | .NET 8.0 | 0.37 ns | 0.99 | - |
| Manual: auto-counter (1 tag) | .NET 8.0 | 0.37 ns | 1.01 | - |
| Generated: auto-counter (1 tag) | .NET 8.0 | 0.36 ns | 0.98 | - |
| Manual: up-down counter | .NET 8.0 | 0.36 ns | 0.97 | - |
| Generated: up-down counter | .NET 8.0 | 0.54 ns | 1.48 | - |
| Manual: histogram (0 tags) | .NET 8.0 | 0.37 ns | 1.00 | - |
| Generated: histogram (0 tags) | .NET 8.0 | 0.38 ns | 1.03 | - |
| Manual: histogram (1 tag) | .NET 8.0 | 0.36 ns | 0.98 | - |
| Generated: histogram (1 tag) | .NET 8.0 | 0.55 ns | 1.49 | - |
| Manual: auto-counter (0 tags) | .NET 9.0 | 0.37 ns | 1.00 | - |
| Generated: auto-counter (0 tags) | .NET 9.0 | 0.18 ns | 0.49 | - |
| Manual: auto-counter (1 tag) | .NET 9.0 | 0.36 ns | 1.00 | - |
| Generated: auto-counter (1 tag) | .NET 9.0 | 0.36 ns | 0.98 | - |
| Manual: up-down counter | .NET 9.0 | 0.35 ns | 0.95 | - |
| Generated: up-down counter | .NET 9.0 | 0.18 ns | 0.50 | - |
| Manual: histogram (0 tags) | .NET 9.0 | 0.18 ns | 0.50 | - |
| Generated: histogram (0 tags) | .NET 9.0 | 0.17 ns | 0.48 | - |
| Manual: histogram (1 tag) | .NET 9.0 | 0.37 ns | 1.03 | - |
| Generated: histogram (1 tag) | .NET 9.0 | 0.19 ns | 0.52 | - |
Interpretation: On .NET 10.0, generated instruments measure ~0.35-0.37 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 and .NET 9.0, 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