@cratis/pi 7.102.0 → 7.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
- package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
- package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
- package/package/corpus/hooks/README.md +11 -1
- package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
- package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
- package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
- package/package/corpus/rules/profiles.md +10 -1
- package/package/corpus/rules/pull-requests.md +2 -0
- package/package/corpus/rules/secrets.md +4 -0
- package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
- package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
- package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
- package/package/corpus/skills/binlog-generation/LICENSE +23 -0
- package/package/corpus/skills/binlog-generation/NOTICE +22 -0
- package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
- package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
- package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
- package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
- package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
- package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
- package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
- package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
- package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
- package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
- package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
- package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
- package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
- package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
- package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
- package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
- package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
- package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
- package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
- package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
- package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
- package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
- package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
- package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
- package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
- package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
- package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
- package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
- package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
- package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
- package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
- package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
- package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
- package/package/corpus/skills/dump-collect/LICENSE +23 -0
- package/package/corpus/skills/dump-collect/NOTICE +40 -0
- package/package/corpus/skills/dump-collect/SKILL.md +97 -0
- package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
- package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
- package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
- package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
- package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
- package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
- package/package/corpus/skills/extension-points/LICENSE +23 -0
- package/package/corpus/skills/extension-points/NOTICE +18 -0
- package/package/corpus/skills/extension-points/SKILL.md +248 -0
- package/package/corpus/skills/filter-syntax/LICENSE +23 -0
- package/package/corpus/skills/filter-syntax/NOTICE +17 -0
- package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
- package/package/corpus/skills/including-generated-files/LICENSE +23 -0
- package/package/corpus/skills/including-generated-files/NOTICE +17 -0
- package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
- package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
- package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
- package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
- package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
- package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
- package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
- package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
- package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
- package/package/corpus/skills/msbuild/LICENSE +23 -0
- package/package/corpus/skills/msbuild/NOTICE +55 -0
- package/package/corpus/skills/msbuild/SKILL.md +117 -0
- package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
- package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
- package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
- package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
- package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
- package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
- package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
- package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
- package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
- package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
- package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
- package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
- package/package/corpus/skills/orleans/LICENSE +21 -0
- package/package/corpus/skills/orleans/NOTICE +64 -0
- package/package/corpus/skills/orleans/SKILL.md +140 -0
- package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
- package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
- package/package/corpus/skills/orleans/references/examples.md +66 -0
- package/package/corpus/skills/orleans/references/grain-api.md +784 -0
- package/package/corpus/skills/orleans/references/grains.md +75 -0
- package/package/corpus/skills/orleans/references/hosting.md +71 -0
- package/package/corpus/skills/orleans/references/implementation.md +591 -0
- package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
- package/package/corpus/skills/orleans/references/patterns.md +811 -0
- package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
- package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
- package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
- package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
- package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
- package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
- package/package/corpus/skills/platform-detection/LICENSE +23 -0
- package/package/corpus/skills/platform-detection/NOTICE +17 -0
- package/package/corpus/skills/platform-detection/SKILL.md +230 -0
- package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
- package/package/corpus/skills/profiling/LICENSE +21 -0
- package/package/corpus/skills/profiling/NOTICE +50 -0
- package/package/corpus/skills/profiling/SKILL.md +144 -0
- package/package/corpus/skills/profiling/references/commands.md +439 -0
- package/package/corpus/skills/profiling/references/patterns.md +586 -0
- package/package/corpus/skills/profiling/references/profiling.md +190 -0
- package/package/corpus/skills/run-tests/LICENSE +23 -0
- package/package/corpus/skills/run-tests/NOTICE +19 -0
- package/package/corpus/skills/run-tests/SKILL.md +305 -0
- package/package/corpus/skills/target-authoring/LICENSE +23 -0
- package/package/corpus/skills/target-authoring/NOTICE +17 -0
- package/package/corpus/skills/target-authoring/SKILL.md +165 -0
- package/package/corpus/skills/vectorization/LICENSE +23 -0
- package/package/corpus/skills/vectorization/NOTICE +17 -0
- package/package/corpus/skills/vectorization/SKILL.md +189 -0
- package/package/profile-catalog.json +85 -1
- package/package.json +1 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Measurement and diagnostics
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Diagnosers
|
|
6
|
+
- Statistical output
|
|
7
|
+
- Outlier handling
|
|
8
|
+
- Export formats
|
|
9
|
+
|
|
10
|
+
BenchmarkDotNet measures wall-clock time by default. This reference covers how to collect additional data (allocations, disassembly, hardware counters), customize statistical output, and export results.
|
|
11
|
+
|
|
12
|
+
## Diagnosers
|
|
13
|
+
|
|
14
|
+
Diagnosers add columns or produce artifacts beyond basic timing. They are enabled via class-level attributes or CLI flags.
|
|
15
|
+
|
|
16
|
+
### MemoryDiagnoser
|
|
17
|
+
|
|
18
|
+
| Surface | Usage |
|
|
19
|
+
|---------|-------|
|
|
20
|
+
| Attribute | `[MemoryDiagnoser]` or `[MemoryDiagnoser(displayGenColumns: false)]` |
|
|
21
|
+
| Config | `.AddDiagnoser(MemoryDiagnoser.Default)` |
|
|
22
|
+
| CLI | `--memory` |
|
|
23
|
+
|
|
24
|
+
Tracks managed heap allocations and GC collections per operation.
|
|
25
|
+
|
|
26
|
+
Adds columns: `Gen0`, `Gen1`, `Gen2` (GC collections per 1000 operations — a value of `1.0000` means one collection per 1000 operations), `Allocated` (bytes per operation).
|
|
27
|
+
|
|
28
|
+
Set `displayGenColumns: false` if only total allocated bytes matter.
|
|
29
|
+
|
|
30
|
+
### DisassemblyDiagnoser
|
|
31
|
+
|
|
32
|
+
| Surface | Usage |
|
|
33
|
+
|---------|-------|
|
|
34
|
+
| Attribute | `[DisassemblyDiagnoser(maxDepth: 2)]` |
|
|
35
|
+
| Config | `.AddDiagnoser(new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig(maxDepth: 2, printSource: true)))` |
|
|
36
|
+
| CLI | `--disasm`, `--disasmDepth N`, `--disasmDiff`, `--disasmFilter "Method*"` |
|
|
37
|
+
|
|
38
|
+
Captures the JIT-compiled assembly code for the benchmark method.
|
|
39
|
+
|
|
40
|
+
Produces: a per-benchmark disassembly report (exported as GitHub Markdown and/or HTML).
|
|
41
|
+
|
|
42
|
+
Config options (`DisassemblyDiagnoserConfig`):
|
|
43
|
+
- `maxDepth` (default 1) — how many levels of called methods to include in the disassembly.
|
|
44
|
+
- `syntax` — `DisassemblySyntax.Masm` (default), `Intel`, or `Att`
|
|
45
|
+
- `printSource` (default false) — include C#/F#/VB source alongside assembly
|
|
46
|
+
- `exportDiff` (default false) — when comparing multiple jobs, export a diff view
|
|
47
|
+
- `exportCombinedDisassemblyReport` (default false) — all benchmarks in a single HTML report for side-by-side comparison
|
|
48
|
+
|
|
49
|
+
When using `printSource: true`, ensure the benchmark project emits PDB files so BDN can map assembly instructions to source lines:
|
|
50
|
+
|
|
51
|
+
```xml
|
|
52
|
+
<DebugType>pdbonly</DebugType>
|
|
53
|
+
<DebugSymbols>true</DebugSymbols>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### ThreadingDiagnoser
|
|
57
|
+
|
|
58
|
+
`[ThreadingDiagnoser]` (CLI: `--threading`, Config: `.AddDiagnoser(ThreadingDiagnoser.Default)`) — adds `Completed Work Items` and `Lock Contentions` columns.
|
|
59
|
+
|
|
60
|
+
### EventPipeProfiler
|
|
61
|
+
|
|
62
|
+
| Surface | Usage |
|
|
63
|
+
|---------|-------|
|
|
64
|
+
| Attribute | `[EventPipeProfiler(EventPipeProfile.CpuSampling)]` |
|
|
65
|
+
| Config | `.AddDiagnoser(new EventPipeProfiler(EventPipeProfile.CpuSampling))` |
|
|
66
|
+
| CLI | `--profiler EP` |
|
|
67
|
+
|
|
68
|
+
Collects CPU sampling profiles using .NET's EventPipe infrastructure. Cross-platform.
|
|
69
|
+
|
|
70
|
+
Profiles available: `CpuSampling`, `GcVerbose`, `GcCollect`, `Jit`.
|
|
71
|
+
|
|
72
|
+
Produces: `.nettrace` and `.speedscope.json` files. By default (attribute/config), runs an extra benchmark iteration dedicated to profiling so timing results are unaffected.
|
|
73
|
+
|
|
74
|
+
**CLI note:** `--profiler EP` uses `CpuSampling` with `performExtraBenchmarksRun: false` — profiling is attached to normal measurement runs (no extra iteration, results include profiler overhead, no way to select a different profile). Use the attribute or config API for full control.
|
|
75
|
+
|
|
76
|
+
### HardwareCounters (Windows only, requires elevation)
|
|
77
|
+
|
|
78
|
+
| Surface | Usage |
|
|
79
|
+
|---------|-------|
|
|
80
|
+
| Attribute | `[HardwareCounters(HardwareCounter.CacheMisses, HardwareCounter.BranchMispredictions)]` |
|
|
81
|
+
| Config | `.AddHardwareCounters(HardwareCounter.CacheMisses, HardwareCounter.BranchMispredictions)` |
|
|
82
|
+
| CLI | `--counters CacheMisses+BranchMispredictions` |
|
|
83
|
+
|
|
84
|
+
Collects CPU hardware performance counters via ETW.
|
|
85
|
+
|
|
86
|
+
Available counters include: `TotalCycles`, `InstructionRetired`, `CacheMisses`, `BranchMispredictions`, `BranchInstructions`, `LlcMisses`, `LlcReference`.
|
|
87
|
+
|
|
88
|
+
Requires: Windows, elevated (admin) process, and ETW support. Not available on all hardware.
|
|
89
|
+
|
|
90
|
+
## Statistical output
|
|
91
|
+
|
|
92
|
+
### Default columns
|
|
93
|
+
|
|
94
|
+
| Column | Meaning |
|
|
95
|
+
|--------|---------|
|
|
96
|
+
| `Mean` | Average per-operation time across all measured iterations |
|
|
97
|
+
| `Error` | Half-width of the 99.9% confidence interval — if large relative to Mean, results are noisy |
|
|
98
|
+
| `StdDev` | Standard deviation of measurements (hidden when negligible) |
|
|
99
|
+
| `Median` | Median per-operation time (auto-shown when data is skewed or median diverges from mean) |
|
|
100
|
+
| `P95` | 95th percentile (auto-shown when extreme outliers are present) |
|
|
101
|
+
| `Ratio` | Performance relative to the baseline (current / baseline) — only shown when a baseline is marked. A value of `0.85` means ~15% faster. See [comparison-strategies.md](comparison-strategies.md) for how this is calculated |
|
|
102
|
+
| `RatioSD` | Standard deviation of the ratio distribution |
|
|
103
|
+
|
|
104
|
+
### Adding columns
|
|
105
|
+
|
|
106
|
+
Additional statistics can be added via attributes on the benchmark class:
|
|
107
|
+
|
|
108
|
+
| Attribute | Column added |
|
|
109
|
+
|-----------|-------------|
|
|
110
|
+
| `[MedianColumn]` | Median (P50) |
|
|
111
|
+
| `[MinColumn]`, `[MaxColumn]` | Minimum, Maximum |
|
|
112
|
+
| `[Q1Column]`, `[Q3Column]` | First quartile (P25), Third quartile (P75) |
|
|
113
|
+
| `[AllStatisticsColumn]` | Mean, StdErr, StdDev, Ops/s, Min, Q1, Median, Q3, Max |
|
|
114
|
+
| `[RankColumn]` | Rank (1, 2, 3...) |
|
|
115
|
+
| `[SkewnessColumn]`, `[KurtosisColumn]` | Distribution shape metrics |
|
|
116
|
+
| `[OperationsPerSecond]` | Throughput (ops/sec) |
|
|
117
|
+
|
|
118
|
+
Percentile columns are available via code: `StatisticColumn.P50`, `StatisticColumn.P85`, `StatisticColumn.P95`, etc.
|
|
119
|
+
|
|
120
|
+
### Hiding columns
|
|
121
|
+
|
|
122
|
+
Use `[HideColumns]` to remove unwanted columns from the output:
|
|
123
|
+
|
|
124
|
+
```csharp
|
|
125
|
+
[HideColumns("Error", "StdDev", "RatioSD")]
|
|
126
|
+
public class MyBenchmark { ... }
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Outlier handling
|
|
130
|
+
|
|
131
|
+
| Surface | Usage |
|
|
132
|
+
|---------|-------|
|
|
133
|
+
| Attribute | `[Outliers(OutlierMode.DontRemove)]` |
|
|
134
|
+
| Config | `.WithOutlierMode(OutlierMode.DontRemove)` |
|
|
135
|
+
| CLI | `--outliers DontRemove` |
|
|
136
|
+
|
|
137
|
+
By default, BDN removes upper outliers (unusually slow iterations, typically caused by GC or OS interference). Available modes: `DontRemove`, `RemoveUpper` (default), `RemoveLower`, `RemoveAll`.
|
|
138
|
+
|
|
139
|
+
## Export formats
|
|
140
|
+
|
|
141
|
+
BDN exports results in multiple formats. By default it produces Markdown and CSV in the `BenchmarkDotNet.Artifacts/results/` directory.
|
|
142
|
+
|
|
143
|
+
| Format | Default | Config | CLI flag |
|
|
144
|
+
|--------|---------|--------|----------|
|
|
145
|
+
| JSON (full, indented) | no | `.AddExporter(JsonExporter.Full)` | `--exporters fulljson` |
|
|
146
|
+
| JSON (compressed) | no | `.AddExporter(JsonExporter.Default)` | `--exporters json` |
|
|
147
|
+
| CSV | yes | `.AddExporter(CsvExporter.Default)` | `--exporters csv` |
|
|
148
|
+
| GitHub Markdown | yes | `.AddExporter(MarkdownExporter.GitHub)` | `--exporters github` |
|
|
149
|
+
|
|
150
|
+
Use `JsonExporter.Full` for individual measurements (needed for programmatic comparison).
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Running benchmarks
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Entry points: BenchmarkSwitcher vs BenchmarkRunner
|
|
6
|
+
- Config objects
|
|
7
|
+
- Creating a new benchmark project
|
|
8
|
+
- Build and run strategy
|
|
9
|
+
- Artifacts directory
|
|
10
|
+
- Useful CLI flags for agents
|
|
11
|
+
- Dry run validation
|
|
12
|
+
|
|
13
|
+
This reference covers how to build, run, and read BenchmarkDotNet results. It also covers common CLI flags for controlling output and debugging.
|
|
14
|
+
|
|
15
|
+
## Entry points: BenchmarkSwitcher vs BenchmarkRunner
|
|
16
|
+
|
|
17
|
+
BenchmarkDotNet programs use one of two entry points. This determines whether CLI arguments work:
|
|
18
|
+
|
|
19
|
+
**BenchmarkSwitcher** — passes `args` to BDN, CLI flags work:
|
|
20
|
+
```csharp
|
|
21
|
+
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
|
|
22
|
+
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); // with config
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**BenchmarkRunner** — only forwards args if explicitly passed:
|
|
26
|
+
```csharp
|
|
27
|
+
BenchmarkRunner.Run<MyBenchmark>(); // CLI flags IGNORED
|
|
28
|
+
BenchmarkRunner.Run<MyBenchmark>(args: args); // CLI flags work
|
|
29
|
+
BenchmarkRunner.Run<MyBenchmark>(config); // config only, no CLI
|
|
30
|
+
BenchmarkRunner.Run<MyBenchmark>(config, args); // both config and CLI
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Before passing CLI flags like `--filter` or `--job`, check which entry point the program uses. If it uses `BenchmarkRunner.Run<T>()` without args, CLI flags will be silently ignored — configuration must be done through attributes and `ManualConfig` instead.
|
|
34
|
+
|
|
35
|
+
**Critical**: when `BenchmarkSwitcher` is used, it prompts the user interactively to select which benchmarks to run unless `--filter` is provided. For AI agents, always pass `--filter` to avoid hanging on an interactive prompt. `--filter "*"` matches everything and is the standard way to run all benchmarks non-interactively.
|
|
36
|
+
|
|
37
|
+
## Config objects
|
|
38
|
+
|
|
39
|
+
Configs can be built in three ways:
|
|
40
|
+
|
|
41
|
+
**Modify the default config** — starts with all default loggers, exporters, and columns:
|
|
42
|
+
```csharp
|
|
43
|
+
var config = DefaultConfig.Instance
|
|
44
|
+
.AddJob(Job.Default.WithRuntime(CoreRuntime.Core90))
|
|
45
|
+
.AddDiagnoser(MemoryDiagnoser.Default);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Subclass ManualConfig** — useful when config is complex or reused across classes:
|
|
49
|
+
```csharp
|
|
50
|
+
public class MyConfig : ManualConfig
|
|
51
|
+
{
|
|
52
|
+
public MyConfig()
|
|
53
|
+
{
|
|
54
|
+
AddJob(Job.Default.WithRuntime(CoreRuntime.Core80));
|
|
55
|
+
AddJob(Job.Default.WithRuntime(CoreRuntime.Core90).AsBaseline());
|
|
56
|
+
AddDiagnoser(MemoryDiagnoser.Default);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Start from empty** — `ManualConfig.CreateEmpty()` removes all defaults (no loggers, no exporters, no columns). Use `ManualConfig.CreateMinimumViable()` instead to start with the default columns and console logger.
|
|
62
|
+
|
|
63
|
+
Configs can be applied in several ways:
|
|
64
|
+
|
|
65
|
+
| Method | Scope |
|
|
66
|
+
|--------|-------|
|
|
67
|
+
| `BenchmarkRunner.Run<T>(config)` | All benchmarks in the run |
|
|
68
|
+
| `BenchmarkSwitcher...Run(args, config)` | All benchmarks in the run |
|
|
69
|
+
| `[Config(typeof(MyConfig))]` on class or assembly | That class, or all classes in the assembly |
|
|
70
|
+
|
|
71
|
+
## Creating a new benchmark project
|
|
72
|
+
|
|
73
|
+
If there is no existing benchmark project to add to, create a new console project with `dotnet new console` and add the BenchmarkDotNet package. Choose a temporary or permanent location based on the scope of the work.
|
|
74
|
+
|
|
75
|
+
Use `dotnet add package BenchmarkDotNet` without specifying a version — `dotnet` resolves the latest compatible version automatically. Pinning to a specific version risks missing support for newer runtimes and CLI features.
|
|
76
|
+
|
|
77
|
+
**Multi-runtime targeting**: to compare across runtimes (e.g., `--runtimes net8.0 net9.0`), the `.csproj` must use the plural `<TargetFrameworks>` property listing all targets:
|
|
78
|
+
|
|
79
|
+
```xml
|
|
80
|
+
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If the project uses the singular `<TargetFramework>` with only one TFM, multi-runtime runs will fail at build time.
|
|
84
|
+
|
|
85
|
+
Then replace `Program.cs` with:
|
|
86
|
+
```csharp
|
|
87
|
+
using BenchmarkDotNet.Running;
|
|
88
|
+
|
|
89
|
+
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Adjust the `ProjectReference` path to match the project under test. If benchmarking code that isn't in a separate project, skip `dotnet add reference` and put the code under test directly in the benchmark project.
|
|
93
|
+
|
|
94
|
+
## Build and run strategy
|
|
95
|
+
|
|
96
|
+
BenchmarkDotNet console output is extremely verbose — hundreds of lines per case showing pilot, warmup, and actual iteration details. Redirect all output to a file to avoid consuming context on verbose iteration output.
|
|
97
|
+
|
|
98
|
+
**Recommended approach**: build first, then run with output redirected to a file. Always pass `--noOverwrite` so each run writes results to a timestamped subdirectory instead of `BenchmarkDotNet.Artifacts/results/` — without it, each run overwrites the previous results, making it easy to get confused about which run the results are for.
|
|
99
|
+
|
|
100
|
+
Each benchmark method can take several minutes. Rather than running all benchmarks at once, use `--filter` to run a subset at a time (e.g. one or two methods per invocation), read the results, then run the next subset. This keeps each invocation short — avoiding session or terminal timeouts — and lets you verify results incrementally.
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
dotnet build -c Release
|
|
104
|
+
dotnet run -c Release --no-build -- --filter "*MethodName" --noOverwrite > benchmark.log 2>&1
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
After each run, read the Markdown report (`*-report-github.md`) for the summary table. Only read `benchmark.log` if you need to investigate errors or unexpected results.
|
|
108
|
+
|
|
109
|
+
## Artifacts directory
|
|
110
|
+
|
|
111
|
+
By default, BDN writes to `BenchmarkDotNet.Artifacts/` relative to the working directory. Override with:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
dotnet run -c Release --no-build -- --artifacts ./my-results
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The artifacts directory contains:
|
|
118
|
+
```
|
|
119
|
+
BenchmarkDotNet.Artifacts/
|
|
120
|
+
├── MyBenchmark-20240101-120000.log # full console log
|
|
121
|
+
└── results/
|
|
122
|
+
├── MyBenchmark-report-github.md # markdown summary table
|
|
123
|
+
└── MyBenchmark-report.csv # CSV data
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Use `--noOverwrite` to prevent overwriting previous results (writes to `BenchmarkDotNet.Artifacts/<timestamp>/` instead of `BenchmarkDotNet.Artifacts/results/`). Use `--artifacts` with different paths when comparing results from separate runs.
|
|
127
|
+
|
|
128
|
+
## Useful CLI flags for agents
|
|
129
|
+
|
|
130
|
+
### Filtering and discovery
|
|
131
|
+
|
|
132
|
+
| Flag | Purpose |
|
|
133
|
+
|------|---------|
|
|
134
|
+
| `--filter "*"` | Run all benchmarks (avoids interactive prompt from BenchmarkSwitcher) |
|
|
135
|
+
| `--filter "*.MethodName"` | Run specific benchmark methods |
|
|
136
|
+
| `--list flat` | List available benchmark names without running them |
|
|
137
|
+
| `--list tree` | List benchmarks in a tree hierarchy |
|
|
138
|
+
| `--allCategories <name>` | Run only benchmarks matching all specified categories |
|
|
139
|
+
| `--anyCategories <name>` | Run benchmarks matching any specified category |
|
|
140
|
+
|
|
141
|
+
Tag benchmarks with `[BenchmarkCategory("name")]` (on methods, classes, or assemblies) to enable category-based filtering.
|
|
142
|
+
|
|
143
|
+
**How `--filter` works**: BDN matches each pattern against two forms of the benchmark name:
|
|
144
|
+
- **Short form**: `Namespace.ClassName.MethodName` (no parameters)
|
|
145
|
+
- **Full form**: `Namespace.ClassName.MethodName(param: value, ...)` (includes parameters when present)
|
|
146
|
+
|
|
147
|
+
Patterns use glob wildcards (`*` = any characters, `?` = single character), are **case-insensitive**, and must match the **entire** name (anchored). Multiple `--filter` values are OR'd — a case runs if it matches any pattern.
|
|
148
|
+
|
|
149
|
+
| Goal | Pattern |
|
|
150
|
+
|------|---------|
|
|
151
|
+
| Run all benchmarks | `--filter "*"` |
|
|
152
|
+
| Run one method by name | `--filter "*MyMethod"` |
|
|
153
|
+
| Run all methods in a class | `--filter "*MyClass*"` |
|
|
154
|
+
| Run a specific class.method | `--filter "MyNamespace.MyClass.MyMethod"` |
|
|
155
|
+
| Same, using wildcard for namespace | `--filter "*MyClass.MyMethod"` |
|
|
156
|
+
| Run two specific methods | `--filter "*MethodA" --filter "*MethodB"` |
|
|
157
|
+
| Run methods matching a substring | `--filter "*Sort*"` |
|
|
158
|
+
|
|
159
|
+
**Tip**: because patterns are anchored, `--filter "MyMethod"` does **not** match `MyNamespace.MyClass.MyMethod` — always use a leading `*` unless matching the short form exactly. Use `--list flat` to see the exact names BDN uses for filtering.
|
|
160
|
+
|
|
161
|
+
**Category-based filtering**: tag benchmarks with `[BenchmarkCategory("name")]` on methods, classes, or assemblies to enable category filtering. Categories are inherited — a method tagged `"Parsing"` on a class tagged `"Strings"` has both categories.
|
|
162
|
+
|
|
163
|
+
| Flag | Behavior |
|
|
164
|
+
|------|----------|
|
|
165
|
+
| `--anyCategories Parsing Sorting` | Run benchmarks that have **at least one** of the listed categories |
|
|
166
|
+
| `--allCategories Strings Parsing` | Run benchmarks that have **all** of the listed categories |
|
|
167
|
+
|
|
168
|
+
When `--filter` and `--allCategories`/`--anyCategories` are used together, a benchmark must satisfy **both** — the glob filter AND the category filter are AND'd together.
|
|
169
|
+
|
|
170
|
+
### Output control
|
|
171
|
+
|
|
172
|
+
| Flag | Purpose |
|
|
173
|
+
|------|---------|
|
|
174
|
+
| `--artifacts ./path` | Change where results are written |
|
|
175
|
+
| `--noOverwrite` | Don't overwrite previous result files |
|
|
176
|
+
| `--exporters json` | Add JSON export (useful for programmatic comparison) |
|
|
177
|
+
| `--join` | Combine results from multiple benchmark classes into a single table |
|
|
178
|
+
| `--hide "Error" "StdDev"` | Hide specific columns from the results table |
|
|
179
|
+
| `--keepFiles` | Keep the generated benchmark project files — useful for debugging build failures |
|
|
180
|
+
|
|
181
|
+
## Dry run validation
|
|
182
|
+
|
|
183
|
+
Always run `--job Dry` first to catch compilation and runtime errors before committing to a full run:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
dotnet run -c Release --no-build -- --filter "*" --job Dry --noOverwrite
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
A dry run executes each case once (<1 second per case) without meaningful measurement. It validates that setup methods work, parameters are valid, and the benchmark compiles and executes. Only after dry validation passes should you run with default or other job presets.
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Benchmark authoring techniques
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Dead code elimination
|
|
6
|
+
- Setup and cleanup
|
|
7
|
+
- OperationsPerInvoke
|
|
8
|
+
- Avoiding loops
|
|
9
|
+
- No side effects
|
|
10
|
+
- Async benchmarks
|
|
11
|
+
- Deferred execution
|
|
12
|
+
- Constant folding
|
|
13
|
+
- Randomness and reproducibility
|
|
14
|
+
- Benchmark class constraints
|
|
15
|
+
- Parameterization summary
|
|
16
|
+
|
|
17
|
+
This reference covers BenchmarkDotNet features and practices for writing correct benchmark methods. Incorrect benchmarks silently produce misleading results — the techniques here prevent common measurement errors.
|
|
18
|
+
|
|
19
|
+
## Dead code elimination
|
|
20
|
+
|
|
21
|
+
The JIT compiler can eliminate code whose result is never used. If a benchmark method returns `void` and doesn't store its result anywhere observable, the JIT may partially or fully optimize away the computation.
|
|
22
|
+
|
|
23
|
+
**Returning a value from the benchmark method prevents this.** When the method has a non-void return type, the JIT cannot prove the result is unused (the call site in BDN's generated harness is opaque to the JIT), so it must preserve the computation:
|
|
24
|
+
|
|
25
|
+
```csharp
|
|
26
|
+
// Correct — non-void return prevents DCE
|
|
27
|
+
[Benchmark]
|
|
28
|
+
public int Parse() => int.Parse("12345");
|
|
29
|
+
|
|
30
|
+
// Wrong — void return, JIT may eliminate the call
|
|
31
|
+
[Benchmark]
|
|
32
|
+
public void Parse() => int.Parse("12345");
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For benchmarks that must return `void` (e.g., methods with side effects like `Array.Sort`), DCE is not a concern because the operation itself has observable effects.
|
|
36
|
+
|
|
37
|
+
For iterating deferred sequences where returning the sequence object doesn't force enumeration, use the `Consumer` class explicitly — see [Deferred execution](#deferred-execution).
|
|
38
|
+
|
|
39
|
+
## Setup and cleanup
|
|
40
|
+
|
|
41
|
+
### GlobalSetup / GlobalCleanup
|
|
42
|
+
|
|
43
|
+
A method marked `[GlobalSetup]` runs once per benchmark case, before all iterations of that case. Use it to allocate buffers, load data, and initialize state that the benchmark reads but does not mutate.
|
|
44
|
+
|
|
45
|
+
```csharp
|
|
46
|
+
private int[] _array;
|
|
47
|
+
|
|
48
|
+
[GlobalSetup]
|
|
49
|
+
public void Setup() => _array = Enumerable.Range(0, 1000).ToArray();
|
|
50
|
+
|
|
51
|
+
[Benchmark]
|
|
52
|
+
public int Sum() => _array.Sum();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Initialization logic inside the benchmark method itself is measured — this is almost never what you want.
|
|
56
|
+
|
|
57
|
+
When a class has multiple benchmarks needing different setup, use the `Target` or `Targets` property:
|
|
58
|
+
|
|
59
|
+
```csharp
|
|
60
|
+
[GlobalSetup(Target = nameof(BenchmarkA))]
|
|
61
|
+
public void SetupA() { ... }
|
|
62
|
+
|
|
63
|
+
[GlobalSetup(Targets = new[] { nameof(BenchmarkB), nameof(BenchmarkC) })]
|
|
64
|
+
public void SetupBC() { ... }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`[GlobalCleanup]` runs once after all iterations. Use it to dispose resources (files, connections) created during setup.
|
|
68
|
+
|
|
69
|
+
**Note**: when `[MemoryRandomization]` is enabled, BDN re-runs `[GlobalCleanup]` and `[GlobalSetup]` after every iteration (overriding the normal once-only behavior). See [references/bdn-internals-and-tuning.md](bdn-internals-and-tuning.md) for details.
|
|
70
|
+
|
|
71
|
+
### IterationSetup / IterationCleanup
|
|
72
|
+
|
|
73
|
+
A method marked `[IterationSetup]` runs before every iteration. Use it only when the benchmark mutates state that must be reset — for example, sorting an array in-place.
|
|
74
|
+
|
|
75
|
+
**Critical constraint**: when `[IterationSetup]` or `[IterationCleanup]` is used, BDN defaults to `InvocationCount=1` and `UnrollFactor=1` (unless explicitly overridden). This means the entire iteration is a single invocation, so the operation should be long enough (generally hundreds of milliseconds or more) for a single-invocation measurement to be reliable — with only one invocation, OS scheduling jitter and timer resolution become a significant fraction of the measured time.
|
|
76
|
+
|
|
77
|
+
```csharp
|
|
78
|
+
[Params(10_000_000)] // large enough for single-invocation measurement
|
|
79
|
+
public int Size { get; set; }
|
|
80
|
+
|
|
81
|
+
private int[] _data, _scratch;
|
|
82
|
+
|
|
83
|
+
[GlobalSetup]
|
|
84
|
+
public void Setup() => _data = Enumerable.Range(0, Size).Reverse().ToArray();
|
|
85
|
+
|
|
86
|
+
[IterationSetup]
|
|
87
|
+
public void ResetArray() => Array.Copy(_data, _scratch = new int[Size], Size);
|
|
88
|
+
|
|
89
|
+
[Benchmark]
|
|
90
|
+
public void Sort() => Array.Sort(_scratch);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**If `[GlobalSetup]` is sufficient, do not use `[IterationSetup]`.** It adds overhead and constrains the measurement.
|
|
94
|
+
|
|
95
|
+
## OperationsPerInvoke
|
|
96
|
+
|
|
97
|
+
When part of the benchmark setup cannot be moved to `[GlobalSetup]` — typically because it involves a stack-only type like `Span<T>` — use `OperationsPerInvoke` to amortize the setup cost by performing multiple logical operations per invocation.
|
|
98
|
+
|
|
99
|
+
```csharp
|
|
100
|
+
private byte[] _array = new byte[18];
|
|
101
|
+
|
|
102
|
+
[Benchmark(OperationsPerInvoke = 16)]
|
|
103
|
+
public Span<byte> Slice()
|
|
104
|
+
{
|
|
105
|
+
Span<byte> span = new Span<byte>(_array); // setup: can't go in GlobalSetup (stack-only)
|
|
106
|
+
|
|
107
|
+
// 16 operations, unrolled without a loop
|
|
108
|
+
span = span.Slice(1); span = span.Slice(1); span = span.Slice(1); span = span.Slice(1);
|
|
109
|
+
span = span.Slice(1); span = span.Slice(1); span = span.Slice(1); span = span.Slice(1);
|
|
110
|
+
span = span.Slice(1); span = span.Slice(1); span = span.Slice(1); span = span.Slice(1);
|
|
111
|
+
span = span.Slice(1); span = span.Slice(1); span = span.Slice(1); span = span.Slice(1);
|
|
112
|
+
|
|
113
|
+
return span;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
BDN divides the measured time by `OperationsPerInvoke`, so the reported result reflects a single `Slice` operation with the `Span` creation cost amortized. The operations are manually unrolled (not in a loop) to avoid loop overhead affecting the measurement.
|
|
118
|
+
|
|
119
|
+
## Avoiding loops
|
|
120
|
+
|
|
121
|
+
BDN determines how many times to invoke the benchmark method per iteration automatically (the pilot stage). Adding a manual loop inside the benchmark is unnecessary and harmful:
|
|
122
|
+
|
|
123
|
+
```csharp
|
|
124
|
+
// Wrong — manual loop adds loop overhead and hides per-operation variance
|
|
125
|
+
[Benchmark]
|
|
126
|
+
public int ParseLoop()
|
|
127
|
+
{
|
|
128
|
+
int result = 0;
|
|
129
|
+
for (int i = 0; i < 1000; i++)
|
|
130
|
+
result += int.Parse("12345");
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Correct — BDN handles invocation count
|
|
135
|
+
[Benchmark]
|
|
136
|
+
public int Parse() => int.Parse("12345");
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The exception is `OperationsPerInvoke` — when per-invocation setup cannot be avoided, repeating the operation inside the method and declaring the count lets BDN divide the result correctly.
|
|
140
|
+
|
|
141
|
+
## No side effects
|
|
142
|
+
|
|
143
|
+
Benchmarks should be idempotent — running the method N times should produce the same performance characteristics as running it once. A benchmark that grows a list on every invocation violates this:
|
|
144
|
+
|
|
145
|
+
```csharp
|
|
146
|
+
// Wrong — list grows with every invocation, later calls are slower
|
|
147
|
+
List<int> _numbers = new();
|
|
148
|
+
|
|
149
|
+
[Benchmark]
|
|
150
|
+
public void Add() => _numbers.Add(12345);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
If the operation inherently mutates state (e.g., `Array.Sort`), use `[IterationSetup]` to reset state between iterations, with input large enough for reliable single-invocation measurement.
|
|
154
|
+
|
|
155
|
+
## Async benchmarks
|
|
156
|
+
|
|
157
|
+
BDN natively supports benchmark methods that return `Task`, `Task<T>`, `ValueTask`, or `ValueTask<T>`. BDN awaits the result automatically — there is no special attribute or configuration needed:
|
|
158
|
+
|
|
159
|
+
```csharp
|
|
160
|
+
[Benchmark]
|
|
161
|
+
public async Task<int> ReadAsync()
|
|
162
|
+
{
|
|
163
|
+
using var stream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true);
|
|
164
|
+
var buffer = new byte[4096];
|
|
165
|
+
return await stream.ReadAsync(buffer);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Async setup/cleanup**: `[GlobalSetup]` and `[GlobalCleanup]` methods can also return `Task` or `ValueTask` — BDN awaits them automatically. `[IterationSetup]` and `[IterationCleanup]` do **not** support async return types; they must be synchronous.
|
|
170
|
+
|
|
171
|
+
**When NOT to use async**: if the operation being benchmarked is synchronous, do not wrap it in `async`/`await`. The async state machine adds overhead that would be included in the measurement:
|
|
172
|
+
|
|
173
|
+
```csharp
|
|
174
|
+
// Wrong — adds async state machine overhead to a synchronous operation
|
|
175
|
+
[Benchmark]
|
|
176
|
+
public async Task<int> ParseAsync() => await Task.FromResult(int.Parse("12345"));
|
|
177
|
+
|
|
178
|
+
// Correct — benchmark the synchronous method directly
|
|
179
|
+
[Benchmark]
|
|
180
|
+
public int Parse() => int.Parse("12345");
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Deferred execution
|
|
184
|
+
|
|
185
|
+
Returning `IEnumerable<T>`, `IQueryable<T>`, or `Lazy<T>` from a benchmark measures only the creation of the deferred sequence — not its execution. This is almost always a bug:
|
|
186
|
+
|
|
187
|
+
```csharp
|
|
188
|
+
// Wrong — measures only the creation of the LINQ query object, not its execution
|
|
189
|
+
[Benchmark]
|
|
190
|
+
public IEnumerable<int> WhereQuery() => _numbers.Where(n => n > 50);
|
|
191
|
+
|
|
192
|
+
// Correct — materialize the result so the query actually executes
|
|
193
|
+
[Benchmark]
|
|
194
|
+
public List<int> WhereQuery() => _numbers.Where(n => n > 50).ToList();
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
BDN includes a `DeferredExecutionValidator` that detects this mistake and raises a validation error, preventing the benchmark from running. To fix it, either materialize the result (`.ToList()`, `.ToArray()`) or consume it manually using the `.Consume()` extension method:
|
|
198
|
+
|
|
199
|
+
```csharp
|
|
200
|
+
private Consumer _consumer = new();
|
|
201
|
+
|
|
202
|
+
[Benchmark]
|
|
203
|
+
public void WhereQuery() => _numbers.Where(n => n > 50).Consume(_consumer);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Constant folding
|
|
207
|
+
|
|
208
|
+
The JIT can evaluate expressions at compile time when all inputs are constants or literals. If a benchmark operates on constant inputs, the JIT may fold the computation into a precomputed result, and the benchmark measures nothing:
|
|
209
|
+
|
|
210
|
+
```csharp
|
|
211
|
+
// Wrong — JIT can compute Math.Sqrt(144.0) at compile time
|
|
212
|
+
[Benchmark]
|
|
213
|
+
public double Sqrt() => Math.Sqrt(144.0);
|
|
214
|
+
|
|
215
|
+
// Correct — field value is not a compile-time constant
|
|
216
|
+
private double _value = 144.0;
|
|
217
|
+
|
|
218
|
+
[Benchmark]
|
|
219
|
+
public double Sqrt() => Math.Sqrt(_value);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Store inputs in fields, `[Params]`, or `[Arguments]` — never as literals or `const` values in the benchmark method.
|
|
223
|
+
|
|
224
|
+
## Randomness and reproducibility
|
|
225
|
+
|
|
226
|
+
When benchmarks use generated input data, use a fixed seed so results are comparable across runs:
|
|
227
|
+
|
|
228
|
+
```csharp
|
|
229
|
+
[GlobalSetup]
|
|
230
|
+
public void Setup()
|
|
231
|
+
{
|
|
232
|
+
var rng = new Random(42); // fixed seed
|
|
233
|
+
_data = new byte[Size];
|
|
234
|
+
rng.NextBytes(_data);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Benchmark class constraints
|
|
239
|
+
|
|
240
|
+
BDN generates source code for a derived type from the benchmark class, which is compiled as part of its generated project. This imposes constraints:
|
|
241
|
+
- The class must be `public`
|
|
242
|
+
- The class must not be `sealed`, `static`, or `abstract`
|
|
243
|
+
- The class must be a `class`, not a `struct` or `ref struct`
|
|
244
|
+
- Benchmark methods must be `public` and instance (not `static`)
|
|
245
|
+
|
|
246
|
+
## Parameterization summary
|
|
247
|
+
|
|
248
|
+
| Feature | Target | Use when |
|
|
249
|
+
|---------|--------|----------|
|
|
250
|
+
| `[Params]` | Field/Property | Values needed in setup; applies to all benchmarks in the class |
|
|
251
|
+
| `[ParamsSource]` | Field/Property | Dynamic or complex values needed in setup |
|
|
252
|
+
| `[ParamsAllValues]` | Field/Property | Enum or bool — test every possible value |
|
|
253
|
+
| `[Arguments]` | Method | Inline values for a specific benchmark method |
|
|
254
|
+
| `[ArgumentsSource]` | Method | Dynamic or complex values for a specific benchmark method |
|
|
255
|
+
| `[GenericTypeArguments]` | Class | Vary type parameters (e.g., value type vs reference type) |
|
|
256
|
+
|
|
257
|
+
`[Params]` applies to all benchmarks in the class. If different benchmarks need different parameter ranges, split them into separate classes.
|
|
258
|
+
|
|
259
|
+
`[Arguments]` and `[ArgumentsSource]` are method-specific and their initialization time is excluded from the measurement. `[ArgumentsSource]` methods return `IEnumerable<T>` for single-parameter benchmarks, or `IEnumerable<object[]>` for multi-parameter benchmarks (each array is one set of arguments). `[ParamsSource]` methods return `IEnumerable<T>` (each element is a single value assigned to the field/property).
|
|
260
|
+
|
|
261
|
+
```csharp
|
|
262
|
+
// Single-parameter: IEnumerable<T>
|
|
263
|
+
[Benchmark]
|
|
264
|
+
[ArgumentsSource(nameof(Inputs))]
|
|
265
|
+
public int Parse(string text) => int.Parse(text);
|
|
266
|
+
|
|
267
|
+
public IEnumerable<string> Inputs()
|
|
268
|
+
{
|
|
269
|
+
yield return "12345";
|
|
270
|
+
yield return "999999999";
|
|
271
|
+
yield return "-42";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Multi-parameter: IEnumerable<object[]>
|
|
275
|
+
[Benchmark]
|
|
276
|
+
[ArgumentsSource(nameof(Inputs))]
|
|
277
|
+
public bool TryParse(string text, NumberStyles style) => int.TryParse(text, style, null, out _);
|
|
278
|
+
|
|
279
|
+
public IEnumerable<object[]> Inputs()
|
|
280
|
+
{
|
|
281
|
+
yield return new object[] { "12345", NumberStyles.Integer };
|
|
282
|
+
yield return new object[] { "0xFF", NumberStyles.HexNumber };
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`[ParamsSource]` works the same way but targets a field/property and applies to all benchmarks in the class:
|
|
287
|
+
|
|
288
|
+
```csharp
|
|
289
|
+
[ParamsSource(nameof(Sizes))]
|
|
290
|
+
public int N;
|
|
291
|
+
|
|
292
|
+
public IEnumerable<int> Sizes() => new[] { 100, 1_000, 10_000 };
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`[GenericTypeArguments]` varies type parameters on a generic benchmark class. Each attribute creates a separate closed generic type that BDN runs independently:
|
|
296
|
+
|
|
297
|
+
```csharp
|
|
298
|
+
[GenericTypeArguments(typeof(int))]
|
|
299
|
+
[GenericTypeArguments(typeof(string))]
|
|
300
|
+
public class CollectionBenchmark<T>
|
|
301
|
+
{
|
|
302
|
+
private List<T> _list;
|
|
303
|
+
|
|
304
|
+
[GlobalSetup]
|
|
305
|
+
public void Setup() => _list = new List<T>(Enumerable.Repeat(default(T)!, 1000));
|
|
306
|
+
|
|
307
|
+
[Benchmark]
|
|
308
|
+
public bool Contains() => _list.Contains(default!);
|
|
309
|
+
}
|
|
310
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) .NET Foundation and Contributors
|
|
4
|
+
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|