Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ artifacts/

.DS_Store
*.snupkg

# BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts/
**/BenchmarkDotNet.Artifacts/
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="Bullseye" Version="6.1.0" />
<PackageVersion Include="AwesomeAssertions" Version="9.3.0" />
<PackageVersion Include="Glob" Version="1.1.9" />
Expand Down
6 changes: 3 additions & 3 deletions src/SharpCompress/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,9 @@
"net10.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "ISahzLHsHY7vrwqr2p1YWZ+gsxoBRtH7gWRDK8fDUst9pp2He0GiesaqEfeX0V8QMCJM3eNEHGGpnIcPjFo2NQ=="
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "kICGrGYEzCNI3wPzfEXcwNHgTvlvVn9yJDhSdRK+oZQy4jvYH529u7O0xf5ocQKzOMjfS07+3z9PKRIjrFMJDA=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
Expand Down
86 changes: 86 additions & 0 deletions tests/SharpCompress.Performance/ArchiveReadBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using SharpCompress.Archives;

namespace SharpCompress.Performance;

/// <summary>
/// Benchmarks for Archive API operations across different formats.
/// Archive API is used for random access to entries with seekable streams.
/// </summary>
[MemoryDiagnoser]
public class ArchiveReadBenchmarks : BenchmarkBase
{
[Benchmark]
public void ZipArchiveRead()
{
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);
}
}

[Benchmark]
public void TarArchiveRead()
{
var path = GetTestArchivePath("Tar.tar");
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);
}
}

[Benchmark]
public void TarGzArchiveRead()
{
var path = GetTestArchivePath("Tar.tar.gz");
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);
}
}

[Benchmark]
public void TarBz2ArchiveRead()
{
var path = GetTestArchivePath("Tar.tar.bz2");
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);
}
}

[Benchmark]
public void SevenZipArchiveRead()
{
var path = GetTestArchivePath("7Zip.LZMA2.7z");
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);
}
}

[Benchmark]
public void RarArchiveRead()
{
var path = GetTestArchivePath("Rar.rar");
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);
}
}
}
47 changes: 47 additions & 0 deletions tests/SharpCompress.Performance/BaselineComparisonBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
37 changes: 37 additions & 0 deletions tests/SharpCompress.Performance/BenchmarkBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.IO;

namespace SharpCompress.Performance;

/// <summary>
/// Base class for all benchmarks providing common setup for test archives path
/// </summary>
public class BenchmarkBase
{
protected readonly string TEST_ARCHIVES_PATH;

public BenchmarkBase()
{
var index = AppDomain.CurrentDomain.BaseDirectory.IndexOf(
"SharpCompress.Performance",
StringComparison.OrdinalIgnoreCase
);

if (index == -1)
{
throw new InvalidOperationException(
"Could not locate SharpCompress.Performance in the base directory path"
);
}

var path = AppDomain.CurrentDomain.BaseDirectory.Substring(0, index);
var solutionBasePath =
Path.GetDirectoryName(path)
?? throw new InvalidOperationException("Could not determine solution base path");

TEST_ARCHIVES_PATH = Path.Combine(solutionBasePath, "TestArchives", "Archives");
}

protected string GetTestArchivePath(string filename) =>
Path.Combine(TEST_ARCHIVES_PATH, filename);
}
56 changes: 9 additions & 47 deletions tests/SharpCompress.Performance/Program.cs
Original file line number Diff line number Diff line change
@@ -1,54 +1,16 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Performance;
using SharpCompress.Readers;
using SharpCompress.Test;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;

var index = AppDomain.CurrentDomain.BaseDirectory.IndexOf(
"SharpCompress.Performance",
StringComparison.OrdinalIgnoreCase
);
var path = AppDomain.CurrentDomain.BaseDirectory.Substring(0, index);
var SOLUTION_BASE_PATH = Path.GetDirectoryName(path) ?? throw new ArgumentNullException();
namespace SharpCompress.Performance;

var TEST_ARCHIVES_PATH = Path.Combine(SOLUTION_BASE_PATH, "TestArchives", "Archives");

//using var _ = JetbrainsProfiler.Memory($"/Users/adam/temp/");
using (var __ = JetbrainsProfiler.Cpu($"/Users/adam/temp/"))
internal class Program
{
var testArchives = new[]
static void Main(string[] args)
{
"Rar.Audio_program.rar",

//"64bitstream.zip.7z",
//"TarWithSymlink.tar.gz"
};
var arcs = testArchives.Select(a => Path.Combine(TEST_ARCHIVES_PATH, a)).ToArray();
// Run all benchmarks in the assembly
var config = DefaultConfig.Instance;

for (int i = 0; i < 50; i++)
{
using var found = ArchiveFactory.Open(arcs[0]);
foreach (var entry in found.Entries.Where(entry => !entry.IsDirectory))
{
Console.WriteLine($"Extracting {entry.Key}");
using var entryStream = entry.OpenEntryStream();
entryStream.CopyTo(Stream.Null);
}
/*using var found = ReaderFactory.Open(arcs[0]);
while (found.MoveToNextEntry())
{
var entry = found.Entry;
if (entry.IsDirectory)
continue;

Console.WriteLine($"Extracting {entry.Key}");
found.WriteEntryTo(Stream.Null);
}*/
// BenchmarkRunner will find all classes with [Benchmark] attributes
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
}

Console.WriteLine("Still running...");
}
await Task.Delay(500);
131 changes: 131 additions & 0 deletions tests/SharpCompress.Performance/README.md
Original file line number Diff line number Diff line change
@@ -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)
Loading