From 5b1d11bc1d8f6db3e423aea11709df5a85a8a970 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 5 Jan 2026 17:31:32 +0000
Subject: [PATCH] Add WriteBenchmarks, BaselineComparisonBenchmarks, and
comprehensive documentation
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
---
.../BaselineComparisonBenchmarks.cs | 47 +++++++
tests/SharpCompress.Performance/README.md | 131 ++++++++++++++++++
.../WriteBenchmarks.cs | 105 ++++++++++++++
3 files changed, 283 insertions(+)
create mode 100644 tests/SharpCompress.Performance/BaselineComparisonBenchmarks.cs
create mode 100644 tests/SharpCompress.Performance/README.md
create mode 100644 tests/SharpCompress.Performance/WriteBenchmarks.cs
diff --git a/tests/SharpCompress.Performance/BaselineComparisonBenchmarks.cs b/tests/SharpCompress.Performance/BaselineComparisonBenchmarks.cs
new file mode 100644
index 00000000..3907858f
--- /dev/null
+++ b/tests/SharpCompress.Performance/BaselineComparisonBenchmarks.cs
@@ -0,0 +1,47 @@
+using System.IO;
+using System.Linq;
+using BenchmarkDotNet.Attributes;
+using SharpCompress.Archives;
+
+namespace SharpCompress.Performance;
+
+///
+/// Benchmarks comparing current code against a baseline.
+/// Use [Baseline] attribute to mark the reference benchmark.
+///
+[MemoryDiagnoser]
+[RankColumn]
+public class BaselineComparisonBenchmarks : BenchmarkBase
+{
+ ///
+ /// Baseline benchmark for Zip archive reading.
+ /// This serves as the reference point for comparison.
+ ///
+ [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);
+ }
+ }
+
+ ///
+ /// Current implementation benchmark for Zip archive reading.
+ /// BenchmarkDotNet will compare this against the baseline.
+ ///
+ [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);
+ }
+ }
+}
diff --git a/tests/SharpCompress.Performance/README.md b/tests/SharpCompress.Performance/README.md
new file mode 100644
index 00000000..9dfc12dc
--- /dev/null
+++ b/tests/SharpCompress.Performance/README.md
@@ -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)
diff --git a/tests/SharpCompress.Performance/WriteBenchmarks.cs b/tests/SharpCompress.Performance/WriteBenchmarks.cs
new file mode 100644
index 00000000..83d22cdd
--- /dev/null
+++ b/tests/SharpCompress.Performance/WriteBenchmarks.cs
@@ -0,0 +1,105 @@
+using System.IO;
+using System.Linq;
+using BenchmarkDotNet.Attributes;
+using SharpCompress.Common;
+using SharpCompress.Writers;
+
+namespace SharpCompress.Performance;
+
+///
+/// Benchmarks for Writer operations.
+/// Tests creating archives with different compression formats using forward-only Writer API.
+///
+[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);
+ }
+ }
+}
+