Add WriteBenchmarks, BaselineComparisonBenchmarks, and comprehensive documentation

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-05 17:31:32 +00:00
parent aa3a40d968
commit 5b1d11bc1d
3 changed files with 283 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using SharpCompress.Archives;
namespace SharpCompress.Performance;
/// <summary>
/// Benchmarks comparing current code against a baseline.
/// Use [Baseline] attribute to mark the reference benchmark.
/// </summary>
[MemoryDiagnoser]
[RankColumn]
public class BaselineComparisonBenchmarks : BenchmarkBase
{
/// <summary>
/// Baseline benchmark for Zip archive reading.
/// This serves as the reference point for comparison.
/// </summary>
[Benchmark(Baseline = true)]
public void ZipArchiveRead_Baseline()
{
var path = GetTestArchivePath("Zip.deflate.zip");
using var archive = ArchiveFactory.Open(path);
foreach (var entry in archive.Entries.Where(e => !e.IsDirectory))
{
using var stream = entry.OpenEntryStream();
stream.CopyTo(Stream.Null);
}
}
/// <summary>
/// Current implementation benchmark for Zip archive reading.
/// BenchmarkDotNet will compare this against the baseline.
/// </summary>
[Benchmark]
public void ZipArchiveRead_Current()
{
var path = GetTestArchivePath("Zip.deflate.zip");
using var archive = ArchiveFactory.Open(path);
foreach (var entry in archive.Entries.Where(e => !e.IsDirectory))
{
using var stream = entry.OpenEntryStream();
stream.CopyTo(Stream.Null);
}
}
}

View File

@@ -0,0 +1,131 @@
# SharpCompress Performance Benchmarks
This project uses [BenchmarkDotNet](https://benchmarkdotnet.org/) to measure and track performance of SharpCompress archive operations.
## Running Benchmarks
### Run All Benchmarks
```bash
cd tests/SharpCompress.Performance
dotnet run -c Release
```
### Run Specific Benchmark Classes
```bash
# Run only Archive API benchmarks
dotnet run -c Release -- --filter "*ArchiveReadBenchmarks*"
# Run only Reader API benchmarks
dotnet run -c Release -- --filter "*ReaderBenchmarks*"
```
### Run Specific Benchmark Methods
```bash
# Run only Zip benchmarks
dotnet run -c Release -- --filter "*Zip*"
# Run a specific method
dotnet run -c Release -- --filter "ArchiveReadBenchmarks.ZipArchiveRead"
```
### Quick Dry Run (for testing)
```bash
dotnet run -c Release -- --job dry
```
## Benchmark Categories
### ArchiveReadBenchmarks
Tests the **Archive API** which provides random access to entries with seekable streams. Covers:
- Zip (deflate compression)
- Tar (uncompressed)
- Tar.gz (gzip compression)
- Tar.bz2 (bzip2 compression)
- 7Zip (LZMA2 compression)
- Rar
### ReaderBenchmarks
Tests the **Reader API** which provides forward-only streaming for non-seekable streams. Covers:
- Zip
- Tar
- Tar.gz
- Tar.bz2
- Rar
### WriteBenchmarks
Tests the **Writer API** for creating archives using forward-only writing. Covers:
- Zip (deflate compression)
- Tar (uncompressed)
- Tar.gz (gzip compression)
### BaselineComparisonBenchmarks
Example benchmark showing how to compare implementations using the `[Baseline]` attribute. The baseline benchmark serves as a reference point, and BenchmarkDotNet calculates the ratio of performance between baseline and other methods.
## Comparing Against Previous Versions
### Using Baseline Attribute
Mark one benchmark with `[Baseline = true]` and BenchmarkDotNet will show relative performance:
```csharp
[Benchmark(Baseline = true)]
public void MethodA() { /* ... */ }
[Benchmark]
public void MethodB() { /* ... */ }
```
Results will show ratios like "1.5x slower" or "0.8x faster" compared to the baseline.
### Using BenchmarkDotNet.Artifacts for Historical Comparison
BenchmarkDotNet saves results to `BenchmarkDotNet.Artifacts/results/`. You can:
1. Run benchmarks and save the results
2. Keep a snapshot of the results file
3. Compare new runs against saved results
### Using Different NuGet Versions (Advanced)
To compare against a published NuGet package:
1. Create a separate benchmark project referencing the NuGet package
2. Use BenchmarkDotNet's `[SimpleJob]` attribute with different runtimes
3. Reference both the local project and NuGet package in different jobs
## Interpreting Results
BenchmarkDotNet provides:
- **Mean**: Average execution time
- **Error**: Half of 99.9% confidence interval
- **StdDev**: Standard deviation of measurements
- **Allocated**: Memory allocated per operation
- **Rank**: Relative ranking (when using `[RankColumn]`)
- **Ratio**: Relative performance vs baseline (when using `[Baseline]`)
## Output Artifacts
Results are saved to `BenchmarkDotNet.Artifacts/results/`:
- `*.csv`: Raw data for further analysis
- `*-report.html`: HTML report with charts
- `*-report-github.md`: Markdown report for GitHub
- `*.log`: Detailed execution log
## Best Practices
1. **Always run in Release mode**: Debug builds have significant overhead
2. **Close other applications**: Minimize system noise during benchmarks
3. **Run multiple times**: Look for consistency across runs
4. **Use appropriate workload**: Ensure benchmarks run for at least 100ms
5. **Track trends**: Compare results over time to detect regressions
6. **Archive results**: Keep snapshots of benchmark results for historical comparison
## CI/CD Integration
Consider adding benchmarks to CI/CD to:
- Detect performance regressions automatically
- Track performance trends over time
- Compare PR performance against main branch
## Additional Resources
- [BenchmarkDotNet Documentation](https://benchmarkdotnet.org/articles/overview.html)
- [BenchmarkDotNet Configuration](https://benchmarkdotnet.org/articles/configs/configs.html)
- [BenchmarkDotNet Baseline](https://benchmarkdotnet.org/articles/features/baselines.html)

View File

@@ -0,0 +1,105 @@
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using SharpCompress.Common;
using SharpCompress.Writers;
namespace SharpCompress.Performance;
/// <summary>
/// Benchmarks for Writer operations.
/// Tests creating archives with different compression formats using forward-only Writer API.
/// </summary>
[MemoryDiagnoser]
public class WriteBenchmarks : BenchmarkBase
{
private string _tempOutputPath = null!;
private readonly string[] _testFiles = null!;
public WriteBenchmarks()
{
// Get some test files to compress
var originalPath = Path.Combine(
Path.GetDirectoryName(TEST_ARCHIVES_PATH)!,
"Original"
);
if (Directory.Exists(originalPath))
{
_testFiles = Directory.GetFiles(originalPath).Take(5).ToArray();
}
}
[GlobalSetup]
public void Setup()
{
_tempOutputPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(_tempOutputPath);
}
[GlobalCleanup]
public void Cleanup()
{
if (Directory.Exists(_tempOutputPath))
{
Directory.Delete(_tempOutputPath, true);
}
}
[Benchmark]
public void ZipWriterWrite()
{
if (_testFiles == null || _testFiles.Length == 0)
return;
var outputFile = Path.Combine(_tempOutputPath, "test.zip");
using var stream = File.Create(outputFile);
using var writer = WriterFactory.Open(
stream,
ArchiveType.Zip,
new WriterOptions(CompressionType.Deflate)
);
foreach (var file in _testFiles)
{
writer.Write(Path.GetFileName(file), file);
}
}
[Benchmark]
public void TarWriterWrite()
{
if (_testFiles == null || _testFiles.Length == 0)
return;
var outputFile = Path.Combine(_tempOutputPath, "test.tar");
using var stream = File.Create(outputFile);
using var writer = WriterFactory.Open(
stream,
ArchiveType.Tar,
new WriterOptions(CompressionType.None)
);
foreach (var file in _testFiles)
{
writer.Write(Path.GetFileName(file), file);
}
}
[Benchmark]
public void TarGzWriterWrite()
{
if (_testFiles == null || _testFiles.Length == 0)
return;
var outputFile = Path.Combine(_tempOutputPath, "test.tar.gz");
using var stream = File.Create(outputFile);
using var writer = WriterFactory.Open(
stream,
ArchiveType.Tar,
new WriterOptions(CompressionType.GZip)
);
foreach (var file in _testFiles)
{
writer.Write(Path.GetFileName(file), file);
}
}
}