@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.
Files changed (140) hide show
  1. package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
  2. package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
  3. package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
  4. package/package/corpus/hooks/README.md +11 -1
  5. package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
  6. package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
  7. package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
  8. package/package/corpus/rules/profiles.md +10 -1
  9. package/package/corpus/rules/pull-requests.md +2 -0
  10. package/package/corpus/rules/secrets.md +4 -0
  11. package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
  12. package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
  13. package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
  14. package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
  15. package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
  16. package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
  17. package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
  18. package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
  19. package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
  20. package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
  21. package/package/corpus/skills/binlog-generation/LICENSE +23 -0
  22. package/package/corpus/skills/binlog-generation/NOTICE +22 -0
  23. package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
  24. package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
  25. package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
  26. package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
  27. package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
  28. package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
  29. package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
  30. package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
  31. package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
  32. package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
  33. package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
  34. package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
  35. package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
  36. package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
  37. package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
  38. package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
  39. package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
  40. package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
  41. package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
  42. package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
  43. package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
  44. package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
  45. package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
  46. package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
  47. package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
  48. package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
  49. package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
  50. package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
  51. package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
  52. package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
  53. package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
  54. package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
  55. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
  56. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
  57. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
  58. package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
  59. package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
  60. package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
  61. package/package/corpus/skills/dump-collect/LICENSE +23 -0
  62. package/package/corpus/skills/dump-collect/NOTICE +40 -0
  63. package/package/corpus/skills/dump-collect/SKILL.md +97 -0
  64. package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
  65. package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
  66. package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
  67. package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
  68. package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
  69. package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
  70. package/package/corpus/skills/extension-points/LICENSE +23 -0
  71. package/package/corpus/skills/extension-points/NOTICE +18 -0
  72. package/package/corpus/skills/extension-points/SKILL.md +248 -0
  73. package/package/corpus/skills/filter-syntax/LICENSE +23 -0
  74. package/package/corpus/skills/filter-syntax/NOTICE +17 -0
  75. package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
  76. package/package/corpus/skills/including-generated-files/LICENSE +23 -0
  77. package/package/corpus/skills/including-generated-files/NOTICE +17 -0
  78. package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
  79. package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
  80. package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
  81. package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
  82. package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
  83. package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
  84. package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
  85. package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
  86. package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
  87. package/package/corpus/skills/msbuild/LICENSE +23 -0
  88. package/package/corpus/skills/msbuild/NOTICE +55 -0
  89. package/package/corpus/skills/msbuild/SKILL.md +117 -0
  90. package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
  91. package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
  92. package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
  93. package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
  94. package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
  95. package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
  96. package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
  97. package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
  98. package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
  99. package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
  100. package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
  101. package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
  102. package/package/corpus/skills/orleans/LICENSE +21 -0
  103. package/package/corpus/skills/orleans/NOTICE +64 -0
  104. package/package/corpus/skills/orleans/SKILL.md +140 -0
  105. package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
  106. package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
  107. package/package/corpus/skills/orleans/references/examples.md +66 -0
  108. package/package/corpus/skills/orleans/references/grain-api.md +784 -0
  109. package/package/corpus/skills/orleans/references/grains.md +75 -0
  110. package/package/corpus/skills/orleans/references/hosting.md +71 -0
  111. package/package/corpus/skills/orleans/references/implementation.md +591 -0
  112. package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
  113. package/package/corpus/skills/orleans/references/patterns.md +811 -0
  114. package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
  115. package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
  116. package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
  117. package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
  118. package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
  119. package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
  120. package/package/corpus/skills/platform-detection/LICENSE +23 -0
  121. package/package/corpus/skills/platform-detection/NOTICE +17 -0
  122. package/package/corpus/skills/platform-detection/SKILL.md +230 -0
  123. package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
  124. package/package/corpus/skills/profiling/LICENSE +21 -0
  125. package/package/corpus/skills/profiling/NOTICE +50 -0
  126. package/package/corpus/skills/profiling/SKILL.md +144 -0
  127. package/package/corpus/skills/profiling/references/commands.md +439 -0
  128. package/package/corpus/skills/profiling/references/patterns.md +586 -0
  129. package/package/corpus/skills/profiling/references/profiling.md +190 -0
  130. package/package/corpus/skills/run-tests/LICENSE +23 -0
  131. package/package/corpus/skills/run-tests/NOTICE +19 -0
  132. package/package/corpus/skills/run-tests/SKILL.md +305 -0
  133. package/package/corpus/skills/target-authoring/LICENSE +23 -0
  134. package/package/corpus/skills/target-authoring/NOTICE +17 -0
  135. package/package/corpus/skills/target-authoring/SKILL.md +165 -0
  136. package/package/corpus/skills/vectorization/LICENSE +23 -0
  137. package/package/corpus/skills/vectorization/NOTICE +17 -0
  138. package/package/corpus/skills/vectorization/SKILL.md +189 -0
  139. package/package/profile-catalog.json +85 -1
  140. 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.