Add automated performance benchmarks with BenchmarkDotNet#1188
Add automated performance benchmarks with BenchmarkDotNet#1188adamhathcock merged 15 commits intomasterfrom
Conversation
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
…performance benchmarks Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
…ks' into copilot/add-performance-benchmarks # Conflicts: # tests/SharpCompress.Performance/Program.cs
|
@copilot change the bash scripts in the github action to be c# in the build folder to do display and comparsion |
…comparison Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Done! Replaced the bash scripts with C# build targets in commit 2fa8196. The GitHub Actions workflow now calls:
Both targets work cross-platform and integrate with GITHUB_STEP_SUMMARY for displaying results in the workflow summary. |
|
@copilot add a function to the build.csproj to generate baseline benchmarks that will be used for comparsion in CI also, add the performance project the ability to use JetbrainsProfiler to run locally to get Cpu and Memory profiles |
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Done in commit bbc664d! Generate Baseline Build Target: dotnet run --project build/build.csproj -- generate-baselineThis automates: building the performance project, running all benchmarks, combining markdown reports into JetBrains Profiler Support: # CPU profiling
dotnet run --project tests/SharpCompress.Performance/SharpCompress.Performance.csproj -c Release -- --profile --type cpu --output ./snapshots
# Memory profiling
dotnet run --project tests/SharpCompress.Performance/SharpCompress.Performance.csproj -c Release -- --profile --type memory --output ./snapshotsThe performance project now detects |
|
@copilot no comparision happens with the results in CompareBenchmarkResults |
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Fixed in commit 118fbbe! The
Example output shows side-by-side comparison with change percentages, making it easy to spot performance issues in CI. |
| Console.WriteLine($"Extracting {entry.Key}"); | ||
| using var entryStream = entry.OpenEntryStream(); | ||
| entryStream.CopyTo(Stream.Null); | ||
| if (args[i].Equals("--type", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length) |
There was a problem hiding this comment.
WARNING: Potential IndexOutOfRangeException
The bounds check i + 1 < args.Length happens before incrementing i, but if the user passes --type as the last argument, args[++i] will still access an out-of-bounds index. Consider using args[i + 1] instead of args[++i] to avoid modifying i before accessing the value.
| else if ( | ||
| args[i].Equals("--output", StringComparison.OrdinalIgnoreCase) | ||
| && i + 1 < args.Length | ||
| ) |
There was a problem hiding this comment.
WARNING: Potential IndexOutOfRangeException
Same issue as line 40 - using args[++i] after the bounds check can still cause an exception.
Code Review SummaryStatus: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (15 files)
|
There was a problem hiding this comment.
Pull request overview
This PR introduces BenchmarkDotNet-based performance benchmarks for SharpCompress’ major archive formats, adds a baseline results snapshot, and wires benchmark execution + reporting into CI via new build targets and a GitHub Actions workflow.
Changes:
- Add BenchmarkDotNet to the performance test project and implement benchmark classes for Zip/Tar/GZip/7Zip/Rar.
- Add build targets to run, display, generate baseline, and compare benchmark results against a repo-stored baseline.
- Add CI workflow to execute benchmarks and publish results as artifacts and in the GitHub Actions summary.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/SharpCompress.Performance/packages.lock.json | Adds BenchmarkDotNet dependency lock for the performance project. |
| tests/SharpCompress.Performance/baseline-results.md | Adds committed baseline benchmark snapshot for CI comparisons. |
| tests/SharpCompress.Performance/SharpCompress.Performance.csproj | Adds BenchmarkDotNet package reference. |
| tests/SharpCompress.Performance/README.md | Documents running benchmarks, baseline generation, CI, and profiling. |
| tests/SharpCompress.Performance/Program.cs | Switches entry point to BenchmarkDotNet runner + optional JetBrains profiler mode. |
| tests/SharpCompress.Performance/Benchmarks/ZipBenchmarks.cs | Adds Zip read/write benchmarks. |
| tests/SharpCompress.Performance/Benchmarks/TarBenchmarks.cs | Adds Tar read/write + tar.gz benchmark. |
| tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs | Adds 7z LZMA/LZMA2 extraction benchmarks. |
| tests/SharpCompress.Performance/Benchmarks/RarBenchmarks.cs | Adds Rar extraction benchmarks. |
| tests/SharpCompress.Performance/Benchmarks/GZipBenchmarks.cs | Adds GZip compression/decompression benchmarks. |
| tests/SharpCompress.Performance/Benchmarks/ArchiveBenchmarkBase.cs | Adds shared archive path resolution logic. |
| src/SharpCompress/packages.lock.json | Updates ILLink task versions in lockfile. |
| build/Program.cs | Adds build targets to display/compare benchmark results and generate baseline; adds parsing/comparison logic. |
| Directory.Packages.props | Adds central package version for BenchmarkDotNet. |
| .gitignore | Ignores BenchmarkDotNet artifacts, baseline artifacts, profiler snapshots, and benchmark-results directory. |
| .github/workflows/performance-benchmarks.yml | Adds CI workflow to run benchmarks, publish artifacts, and compare vs baseline. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - name: Run Benchmarks | ||
| run: dotnet run --project tests/SharpCompress.Performance/SharpCompress.Performance.csproj --configuration Release --no-build -- --filter "*" --exporters json markdown --artifacts benchmark-results | ||
| continue-on-error: true |
There was a problem hiding this comment.
continue-on-error: true on the benchmark run step will mask benchmark execution failures (including crashes) and still produce a “successful” workflow run. If benchmark failures should be visible/actionable, remove continue-on-error or make the comparison step fail the job when results are missing/regressions are detected.
| foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) | ||
| { | ||
| using var entryStream = entry.OpenEntryStream(); | ||
| entryStream.CopyTo(Stream.Null); | ||
| } |
There was a problem hiding this comment.
This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.
| foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) | ||
| { | ||
| using var entryStream = entry.OpenEntryStream(); | ||
| entryStream.CopyTo(Stream.Null); |
There was a problem hiding this comment.
This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.
| foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) | |
| { | |
| using var entryStream = entry.OpenEntryStream(); | |
| entryStream.CopyTo(Stream.Null); | |
| foreach (var entryStream in archive.Entries.Where(e => !e.IsDirectory).Select(e => e.OpenEntryStream())) | |
| { | |
| using (entryStream) | |
| { | |
| entryStream.CopyTo(Stream.Null); | |
| } |
| foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) | ||
| { | ||
| using var entryStream = entry.OpenEntryStream(); | ||
| entryStream.CopyTo(Stream.Null); | ||
| } |
There was a problem hiding this comment.
This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.
| foreach (var entry in archive.Entries.Where(e => !e.IsDirectory)) | ||
| { | ||
| using var entryStream = entry.OpenEntryStream(); | ||
| entryStream.CopyTo(Stream.Null); | ||
| } |
There was a problem hiding this comment.
This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.
|
|
||
| static double CalculateChange(double baseline, double current) | ||
| { | ||
| if (baseline == 0) |
There was a problem hiding this comment.
Equality checks on floating point values can yield unexpected results.
| if (baseline == 0) | |
| const double Epsilon = 1e-9; | |
| if (Math.Abs(baseline) < Epsilon) |
Implementation Plan for BenchmarkDotNet Performance Benchmarks
✅ All tasks completed!
Summary
This PR implements comprehensive performance benchmarks using BenchmarkDotNet for all major SharpCompress formats. The benchmarks:
✅ Run automatically in CI on push/PR to master and release branches
✅ Compare against baseline results stored in the repo with actual diff analysis
✅ Test CPU and memory usage for all supported archive formats
✅ Use minimal iterations for fast CI execution
✅ Display results in GitHub Actions summary using C# build targets
✅ Include detailed documentation
Latest update:
The
compare-benchmark-resultstarget now performs actual comparison:Comparison Output Example
See
tests/SharpCompress.Performance/README.mdfor complete details.Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.