-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Add performance-benchmark skill for ad hoc benchmarking with EgorBot #123319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
5ab11c8
Initial plan
Copilot cc0c486
Add performance-benchmark skill for ad hoc benchmarking with EgorBot
Copilot b01040b
Fix missing System.Linq using statements in benchmark examples
Copilot 4059503
Remove ICollection<TSource> check from Any() method
Copilot 3a84d8d
Update Best Practices based on Microbenchmark Design Guidelines
Copilot 2874ee7
Remove Baseline attribute from example and Azure targets preferred bu…
Copilot 66c2019
Revert ICollection<TSource> check removal in Any() method
Copilot 1775846
Update target flags guidance based on EgorBo's recommendations
Copilot 74bc8d1
Remove ICollection<TSource> check from Any() method for benchma…
Copilot 065b524
Revert ICollection<TSource> check removal in Any() method
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,254 @@ | ||
| --- | ||
| name: performance-benchmark | ||
| description: Generate and run ad hoc performance benchmarks to validate code changes. Use this when asked to benchmark, profile, or validate the performance impact of a code change in dotnet/runtime. | ||
| --- | ||
|
|
||
| # Ad Hoc Performance Benchmarking | ||
|
|
||
| When you need to validate the performance impact of a code change, follow this process to write a BenchmarkDotNet benchmark and trigger EgorBot to run it. | ||
|
|
||
| ## Step 1: Write the Benchmark | ||
|
|
||
| Create a BenchmarkDotNet benchmark that tests the specific operation being changed. Follow these guidelines: | ||
|
|
||
| ### Benchmark Structure | ||
|
|
||
| ```csharp | ||
| using BenchmarkDotNet.Attributes; | ||
| using BenchmarkDotNet.Running; | ||
|
|
||
| BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args); | ||
|
|
||
| public class Bench | ||
| { | ||
| // Add setup/cleanup if needed | ||
| [GlobalSetup] | ||
| public void Setup() | ||
| { | ||
| // Initialize test data | ||
| } | ||
|
|
||
| [Benchmark] | ||
| public void MyOperation() | ||
| { | ||
| // Test the operation | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Best Practices | ||
|
|
||
| For comprehensive guidance, see the [Microbenchmark Design Guidelines](https://github.com/dotnet/performance/blob/main/docs/microbenchmark-design-guidelines.md). | ||
|
|
||
| Key principles: | ||
|
|
||
| - **Move initialization to `[GlobalSetup]`**: Separate setup logic from the measured code to avoid measuring allocation/initialization overhead | ||
| - **Return values** from benchmark methods to prevent dead code elimination | ||
| - **Avoid loops**: BenchmarkDotNet invokes the benchmark many times automatically; adding manual loops distorts measurements | ||
| - **No side effects**: Benchmarks should be pure and produce consistent results | ||
| - **Focus on common cases**: Benchmark hot paths and typical usage, not edge cases or error paths | ||
| - **Use consistent input data**: Always use the same test data for reproducible comparisons | ||
| - **Avoid `[DisassemblyDiagnoser]`**: It causes crashes on Linux. Use `--envvars DOTNET_JitDisasm:MethodName` instead | ||
| - **Benchmark class requirements**: Must be `public`, not `sealed`, not `static`, and must be a `class` (not struct) | ||
|
|
||
| ### Example: String Operation Benchmark | ||
|
|
||
| ```csharp | ||
| using BenchmarkDotNet.Attributes; | ||
| using BenchmarkDotNet.Running; | ||
|
|
||
| BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args); | ||
|
|
||
| [MemoryDiagnoser] | ||
| public class Bench | ||
| { | ||
| private string _testString = default!; | ||
|
|
||
| [Params(10, 100, 1000)] | ||
| public int Length { get; set; } | ||
|
|
||
| [GlobalSetup] | ||
| public void Setup() | ||
| { | ||
| _testString = new string('a', Length); | ||
| } | ||
|
|
||
| [Benchmark] | ||
| public int StringOperation() | ||
| { | ||
| return _testString.IndexOf('z'); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Example: Collection Operation Benchmark | ||
|
|
||
| ```csharp | ||
| using System.Linq; | ||
| using BenchmarkDotNet.Attributes; | ||
| using BenchmarkDotNet.Running; | ||
|
|
||
| BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args); | ||
|
|
||
| [MemoryDiagnoser] | ||
| public class Bench | ||
| { | ||
| private int[] _array = default!; | ||
| private List<int> _list = default!; | ||
|
|
||
| [Params(100, 1000, 10000)] | ||
| public int Count { get; set; } | ||
|
|
||
| [GlobalSetup] | ||
| public void Setup() | ||
| { | ||
| _array = Enumerable.Range(0, Count).ToArray(); | ||
| _list = _array.ToList(); | ||
| } | ||
|
|
||
| [Benchmark] | ||
| public bool AnyArray() => _array.Any(); | ||
|
|
||
| [Benchmark] | ||
| public bool AnyList() => _list.Any(); | ||
|
|
||
| [Benchmark] | ||
| public int SumArray() => _array.Sum(); | ||
|
|
||
| [Benchmark] | ||
| public int SumList() => _list.Sum(); | ||
| } | ||
| ``` | ||
|
|
||
| ## Step 2: Post the EgorBot Comment | ||
|
|
||
| Post a comment on the PR to trigger EgorBot with your benchmark. The general format is: | ||
|
|
||
| ``` | ||
| @EgorBot [target flags] [options] [BenchmarkDotNet args] | ||
|
|
||
| ```cs | ||
| // Your benchmark code here | ||
| ``` | ||
| ``` | ||
|
|
||
| ### Target Flags (Required - Choose at Least One) | ||
|
|
||
| | Flag | Architecture | Description | | ||
| |------|--------------|-------------| | ||
| | `-x64` or `-amd` | x64 | Linux Azure Genoa (AMD EPYC) - default x64 target | | ||
| | `-arm` | ARM64 | Linux Azure Cobalt100 (Neoverse-N2) | | ||
| | `-intel` | x64 | Azure Cascade Lake (more flaky due to JCC Erratum and loop alignment sensitivity) | | ||
| | `-windows_x64` | x64 | Windows x64 (when Windows-specific testing is needed) | | ||
|
|
||
| **Choosing targets:** | ||
|
|
||
| - **Default for most changes**: Use `-x64` for quick verification of non-architecture/non-OS specific changes | ||
| - **Default when ARM might differ**: Use `-x64 -arm` if there's any suspicion the change might behave differently on ARM | ||
| - **Windows-specific changes**: Use `-windows_x64` when Windows behavior needs testing | ||
| - **Noisy results suspected**: Use `-arm -intel -amd` to get results from multiple x64 CPUs (note: `-intel` targets are more flaky) | ||
|
|
||
| ### Common Options | ||
|
|
||
| | Option | Description | | ||
| |--------|-------------| | ||
| | `-profiler` | Collect flamegraph/hot assembly using perf record | | ||
| | `--envvars KEY:VALUE` | Set environment variables (e.g., `DOTNET_JitDisasm:MethodName`) | | ||
| | `-commit <hash>` | Run against a specific commit | | ||
| | `-commit <hash1> vs <hash2>` | Compare two commits | | ||
| | `-commit <hash> vs previous` | Compare commit with its parent | | ||
stephentoub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ### Example: Basic PR Benchmark | ||
|
|
||
| To benchmark the current PR changes against the base branch: | ||
|
|
||
| ``` | ||
| @EgorBot -x64 -arm | ||
|
|
||
| ```cs | ||
| using BenchmarkDotNet.Attributes; | ||
| using BenchmarkDotNet.Running; | ||
|
|
||
| BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args); | ||
|
|
||
| [MemoryDiagnoser] | ||
| public class Bench | ||
| { | ||
| [Benchmark] | ||
| public int MyOperation() | ||
| { | ||
| // Your benchmark code | ||
| return 42; | ||
| } | ||
| } | ||
| ``` | ||
| ``` | ||
|
|
||
| ### Example: Benchmark with Profiling and Disassembly | ||
|
|
||
| ``` | ||
| @EgorBot -x64 -profiler --envvars DOTNET_JitDisasm:SumArray | ||
|
|
||
| ```cs | ||
| using System.Linq; | ||
| using BenchmarkDotNet.Attributes; | ||
| using BenchmarkDotNet.Running; | ||
|
|
||
| BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args); | ||
|
|
||
| public class Bench | ||
| { | ||
| private int[] _data = Enumerable.Range(0, 1000).ToArray(); | ||
|
|
||
| [Benchmark] | ||
| public int SumArray() => _data.Sum(); | ||
| } | ||
| ``` | ||
| ``` | ||
|
|
||
| ### Example: Compare Two Commits | ||
|
|
||
| ``` | ||
| @EgorBot -amd -commit abc1234 vs def5678 | ||
|
|
||
| ```cs | ||
| using BenchmarkDotNet.Attributes; | ||
| using BenchmarkDotNet.Running; | ||
|
|
||
| BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args); | ||
|
|
||
| public class Bench | ||
| { | ||
| [Benchmark] | ||
| public void TestMethod() | ||
| { | ||
| // Benchmark code | ||
| } | ||
| } | ||
| ``` | ||
| ``` | ||
|
|
||
| ### Example: Run Existing dotnet/performance Benchmarks | ||
|
|
||
| To run benchmarks from the dotnet/performance repository (no code snippet needed): | ||
|
|
||
| ``` | ||
| @EgorBot -arm -intel --filter `*TryGetValueFalse<String, String>*` | ||
| ``` | ||
|
|
||
| **Note**: Surround filter expressions with backticks to avoid issues with special characters. | ||
|
|
||
| ## Important Notes | ||
|
|
||
| - **Bot response time**: EgorBot uses polling and may take up to 30 seconds to respond | ||
| - **Supported repositories**: EgorBot monitors `dotnet/runtime` and `EgorBot/runtime-utils` | ||
| - **PR mode (default)**: When posting in a PR, EgorBot automatically compares the PR changes against the base branch | ||
| - **Results variability**: Results may vary between runs due to VM differences. Do not compare results across different architectures or cloud providers | ||
| - **Check the manual**: EgorBot replies include a link to the [manual](https://github.com/EgorBot/runtime-utils) for advanced options | ||
|
|
||
| ## Additional Resources | ||
|
|
||
| - [Microbenchmark Design Guidelines](https://github.com/dotnet/performance/blob/main/docs/microbenchmark-design-guidelines.md) - Essential reading for writing effective benchmarks | ||
| - [BenchmarkDotNet CLI Arguments](https://github.com/dotnet/BenchmarkDotNet/blob/master/docs/articles/guides/console-args.md) | ||
| - [EgorBot Manual](https://github.com/EgorBot/runtime-utils) | ||
| - [BenchmarkDotNet Filter Simulator](http://egorbot.westus2.cloudapp.azure.com:5042/microbenchmarks) | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.