Skip to content

Commit 06bd6d9

Browse files
authored
Merge pull request #1202 from adamhathcock/adam/async-benchmarks
update benchmarks to include async paths
2 parents 33e9c78 + 98d0f19 commit 06bd6d9

9 files changed

Lines changed: 266 additions & 36 deletions

File tree

AGENTS.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,11 @@ tests/
103103
### Factory Pattern
104104
Factory implementations can implement one or more interfaces (`IArchiveFactory`, `IReaderFactory`, `IWriterFactory`) depending on format capabilities:
105105
- `ArchiveFactory.OpenArchive()` - Opens archive API objects from seekable streams/files
106+
- `ArchiveFactory.OpenAsyncArchive()` - Opens async archive API objects for async archive use cases
106107
- `ReaderFactory.OpenReader()` - Auto-detects and opens forward-only readers
108+
- `ReaderFactory.OpenAsyncReader()` - Auto-detects and opens forward-only async readers
107109
- `WriterFactory.OpenWriter()` - Creates a writer for a specified `ArchiveType`
110+
- `WriterFactory.OpenAsyncWriter()` - Creates an async writer for async write scenarios
108111
- Factories located in: `src/SharpCompress/Factories/`
109112

110113
## Nullable Reference Types
@@ -132,6 +135,9 @@ SharpCompress supports multiple archive and compression formats:
132135
### Async/Await Patterns
133136
- All I/O operations support async/await with `CancellationToken`
134137
- Async methods follow the naming convention: `MethodNameAsync`
138+
- For async archive scenarios, prefer `ArchiveFactory.OpenAsyncArchive(...)` over sync `OpenArchive(...)`.
139+
- For async forward-only read scenarios, prefer `ReaderFactory.OpenAsyncReader(...)` over sync `OpenReader(...)`.
140+
- For async write scenarios, prefer `WriterFactory.OpenAsyncWriter(...)` over sync `OpenWriter(...)`.
135141
- Key async methods:
136142
- `WriteEntryToAsync` - Extract entry asynchronously
137143
- `WriteAllToDirectoryAsync` - Extract all entries asynchronously
@@ -199,7 +205,8 @@ SharpCompress supports multiple archive and compression formats:
199205
## Common Pitfalls
200206

201207
1. **Don't mix Archive and Reader APIs** - Archive needs seekable stream, Reader doesn't
202-
2. **Solid archives (Rar, 7Zip)** - Use `ExtractAllEntries()` for best performance, not individual entry extraction
203-
3. **Stream disposal** - Always set `LeaveStreamOpen` explicitly when needed (default is to close)
204-
4. **Tar + non-seekable stream** - Must provide file size or it will throw
205-
5. **Format detection** - Use `ReaderFactory.OpenReader()` for auto-detection, test with actual archive files
208+
2. **Don't mix sync and async open paths** - For async workflows use `OpenAsyncArchive`/`OpenAsyncReader`/`OpenAsyncWriter`, not `OpenArchive`/`OpenReader`/`OpenWriter`
209+
3. **Solid archives (Rar, 7Zip)** - Use `ExtractAllEntries()` for best performance, not individual entry extraction
210+
4. **Stream disposal** - Always set `LeaveStreamOpen` explicitly when needed (default is to close)
211+
5. **Tar + non-seekable stream** - Must provide file size or it will throw
212+
6. **Format detection** - Use `ReaderFactory.OpenReader()` / `ReaderFactory.OpenAsyncReader()` for auto-detection, test with actual archive files

tests/SharpCompress.Performance/Benchmarks/GZipBenchmarks.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using System.Threading.Tasks;
34
using BenchmarkDotNet.Attributes;
45
using SharpCompress.Compressors;
56
using SharpCompress.Compressors.Deflate;
@@ -36,11 +37,27 @@ public void GZipCompress()
3637
gzipStream.Write(_sourceData, 0, _sourceData.Length);
3738
}
3839

40+
[Benchmark(Description = "GZip: Compress 100KB (Async)")]
41+
public async Task GZipCompressAsync()
42+
{
43+
using var outputStream = new MemoryStream();
44+
using var gzipStream = new GZipStream(outputStream, CompressionMode.Compress);
45+
await gzipStream.WriteAsync(_sourceData, 0, _sourceData.Length).ConfigureAwait(false);
46+
}
47+
3948
[Benchmark(Description = "GZip: Decompress 100KB")]
4049
public void GZipDecompress()
4150
{
4251
using var inputStream = new MemoryStream(_compressedData);
4352
using var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress);
4453
gzipStream.CopyTo(Stream.Null);
4554
}
55+
56+
[Benchmark(Description = "GZip: Decompress 100KB (Async)")]
57+
public async Task GZipDecompressAsync()
58+
{
59+
using var inputStream = new MemoryStream(_compressedData);
60+
using var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress);
61+
await gzipStream.CopyToAsync(Stream.Null).ConfigureAwait(false);
62+
}
4663
}

tests/SharpCompress.Performance/Benchmarks/RarBenchmarks.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.IO;
33
using System.Linq;
4+
using System.Threading.Tasks;
45
using BenchmarkDotNet.Attributes;
56
using SharpCompress.Archives.Rar;
67
using SharpCompress.Readers;
@@ -30,6 +31,18 @@ public void RarExtractArchiveApi()
3031
}
3132
}
3233

34+
[Benchmark(Description = "Rar: Extract all entries (Archive API, Async)")]
35+
public async Task RarExtractArchiveApiAsync()
36+
{
37+
using var stream = new MemoryStream(_rarBytes);
38+
await using var archive = RarArchive.OpenAsyncArchive(stream);
39+
await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory))
40+
{
41+
await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false);
42+
await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false);
43+
}
44+
}
45+
3346
[Benchmark(Description = "Rar: Extract all entries (Reader API)")]
3447
public void RarExtractReaderApi()
3548
{
@@ -43,4 +56,18 @@ public void RarExtractReaderApi()
4356
}
4457
}
4558
}
59+
60+
[Benchmark(Description = "Rar: Extract all entries (Reader API, Async)")]
61+
public async Task RarExtractReaderApiAsync()
62+
{
63+
using var stream = new MemoryStream(_rarBytes);
64+
await using var reader = await ReaderFactory.OpenAsyncReader(stream).ConfigureAwait(false);
65+
while (await reader.MoveToNextEntryAsync().ConfigureAwait(false))
66+
{
67+
if (!reader.Entry.IsDirectory)
68+
{
69+
await reader.WriteEntryToAsync(Stream.Null).ConfigureAwait(false);
70+
}
71+
}
72+
}
4673
}

tests/SharpCompress.Performance/Benchmarks/SevenZipBenchmarks.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.IO;
33
using System.Linq;
4+
using System.Threading.Tasks;
45
using BenchmarkDotNet.Attributes;
56
using SharpCompress.Archives.SevenZip;
67

@@ -31,6 +32,18 @@ public void SevenZipLzmaExtract()
3132
}
3233
}
3334

35+
[Benchmark(Description = "7Zip LZMA: Extract all entries (Async)")]
36+
public async Task SevenZipLzmaExtractAsync()
37+
{
38+
using var stream = new MemoryStream(_lzmaBytes);
39+
await using var archive = SevenZipArchive.OpenAsyncArchive(stream);
40+
await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory))
41+
{
42+
await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false);
43+
await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false);
44+
}
45+
}
46+
3447
[Benchmark(Description = "7Zip LZMA2: Extract all entries")]
3548
public void SevenZipLzma2Extract()
3649
{
@@ -42,4 +55,42 @@ public void SevenZipLzma2Extract()
4255
entryStream.CopyTo(Stream.Null);
4356
}
4457
}
58+
59+
[Benchmark(Description = "7Zip LZMA2: Extract all entries (Async)")]
60+
public async Task SevenZipLzma2ExtractAsync()
61+
{
62+
using var stream = new MemoryStream(_lzma2Bytes);
63+
await using var archive = SevenZipArchive.OpenAsyncArchive(stream);
64+
await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory))
65+
{
66+
await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false);
67+
await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false);
68+
}
69+
}
70+
71+
[Benchmark(Description = "7Zip LZMA2 Reader: Extract all entries")]
72+
public void SevenZipLzma2Extract_Reader()
73+
{
74+
using var stream = new MemoryStream(_lzma2Bytes);
75+
using var archive = SevenZipArchive.OpenArchive(stream);
76+
using var reader = archive.ExtractAllEntries();
77+
foreach (var entry in archive.Entries.Where(e => !e.IsDirectory))
78+
{
79+
using var entryStream = entry.OpenEntryStream();
80+
entryStream.CopyTo(Stream.Null);
81+
}
82+
}
83+
84+
[Benchmark(Description = "7Zip LZMA2 Reader: Extract all entries (Async)")]
85+
public async Task SevenZipLzma2ExtractAsync_Reader()
86+
{
87+
using var stream = new MemoryStream(_lzma2Bytes);
88+
await using var archive = SevenZipArchive.OpenAsyncArchive(stream);
89+
await using var reader = await archive.ExtractAllEntriesAsync();
90+
while (await reader.MoveToNextEntryAsync().ConfigureAwait(false))
91+
{
92+
await using var entryStream = await reader.OpenEntryStreamAsync().ConfigureAwait(false);
93+
await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false);
94+
}
95+
}
4596
}

tests/SharpCompress.Performance/Benchmarks/TarBenchmarks.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.IO;
33
using System.Linq;
4+
using System.Threading.Tasks;
45
using BenchmarkDotNet.Attributes;
56
using SharpCompress.Archives.Tar;
67
using SharpCompress.Common;
@@ -34,6 +35,18 @@ public void TarExtractArchiveApi()
3435
}
3536
}
3637

38+
[Benchmark(Description = "Tar: Extract all entries (Archive API, Async)")]
39+
public async Task TarExtractArchiveApiAsync()
40+
{
41+
using var stream = new MemoryStream(_tarBytes);
42+
await using var archive = TarArchive.OpenAsyncArchive(stream);
43+
await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory))
44+
{
45+
await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false);
46+
await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false);
47+
}
48+
}
49+
3750
[Benchmark(Description = "Tar: Extract all entries (Reader API)")]
3851
public void TarExtractReaderApi()
3952
{
@@ -48,6 +61,20 @@ public void TarExtractReaderApi()
4861
}
4962
}
5063

64+
[Benchmark(Description = "Tar: Extract all entries (Reader API, Async)")]
65+
public async Task TarExtractReaderApiAsync()
66+
{
67+
using var stream = new MemoryStream(_tarBytes);
68+
await using var reader = await ReaderFactory.OpenAsyncReader(stream).ConfigureAwait(false);
69+
while (await reader.MoveToNextEntryAsync().ConfigureAwait(false))
70+
{
71+
if (!reader.Entry.IsDirectory)
72+
{
73+
await reader.WriteEntryToAsync(Stream.Null).ConfigureAwait(false);
74+
}
75+
}
76+
}
77+
5178
[Benchmark(Description = "Tar.GZip: Extract all entries")]
5279
public void TarGzipExtract()
5380
{
@@ -60,6 +87,18 @@ public void TarGzipExtract()
6087
}
6188
}
6289

90+
[Benchmark(Description = "Tar.GZip: Extract all entries (Async)")]
91+
public async Task TarGzipExtractAsync()
92+
{
93+
using var stream = new MemoryStream(_tarGzBytes);
94+
await using var archive = TarArchive.OpenAsyncArchive(stream);
95+
await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory))
96+
{
97+
await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false);
98+
await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false);
99+
}
100+
}
101+
63102
[Benchmark(Description = "Tar: Create archive with small files")]
64103
public void TarCreateSmallFiles()
65104
{
@@ -78,4 +117,22 @@ public void TarCreateSmallFiles()
78117
writer.Write($"file{i}.txt", entryStream);
79118
}
80119
}
120+
121+
[Benchmark(Description = "Tar: Create archive with small files (Async)")]
122+
public async Task TarCreateSmallFilesAsync()
123+
{
124+
using var outputStream = new MemoryStream();
125+
await using var writer = WriterFactory.OpenAsyncWriter(
126+
outputStream,
127+
ArchiveType.Tar,
128+
new WriterOptions(CompressionType.None) { LeaveStreamOpen = true }
129+
);
130+
131+
for (int i = 0; i < 10; i++)
132+
{
133+
var data = new byte[1024];
134+
using var entryStream = new MemoryStream(data);
135+
await writer.WriteAsync($"file{i}.txt", entryStream).ConfigureAwait(false);
136+
}
137+
}
81138
}

tests/SharpCompress.Performance/Benchmarks/ZipBenchmarks.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.IO;
33
using System.Linq;
4+
using System.Threading.Tasks;
45
using BenchmarkDotNet.Attributes;
56
using SharpCompress.Archives.Zip;
67
using SharpCompress.Common;
@@ -34,6 +35,18 @@ public void ZipExtractArchiveApi()
3435
}
3536
}
3637

38+
[Benchmark(Description = "Zip: Extract all entries (Archive API, Async)")]
39+
public async Task ZipExtractArchiveApiAsync()
40+
{
41+
using var stream = new MemoryStream(_archiveBytes);
42+
await using var archive = ZipArchive.OpenAsyncArchive(stream);
43+
await foreach (var entry in archive.EntriesAsync.Where(e => !e.IsDirectory))
44+
{
45+
await using var entryStream = await entry.OpenEntryStreamAsync().ConfigureAwait(false);
46+
await entryStream.CopyToAsync(Stream.Null).ConfigureAwait(false);
47+
}
48+
}
49+
3750
[Benchmark(Description = "Zip: Extract all entries (Reader API)")]
3851
public void ZipExtractReaderApi()
3952
{
@@ -48,6 +61,20 @@ public void ZipExtractReaderApi()
4861
}
4962
}
5063

64+
[Benchmark(Description = "Zip: Extract all entries (Reader API, Async)")]
65+
public async Task ZipExtractReaderApiAsync()
66+
{
67+
using var stream = new MemoryStream(_archiveBytes);
68+
await using var reader = await ReaderFactory.OpenAsyncReader(stream).ConfigureAwait(false);
69+
while (await reader.MoveToNextEntryAsync().ConfigureAwait(false))
70+
{
71+
if (!reader.Entry.IsDirectory)
72+
{
73+
await reader.WriteEntryToAsync(Stream.Null).ConfigureAwait(false);
74+
}
75+
}
76+
}
77+
5178
[Benchmark(Description = "Zip: Create archive with small files")]
5279
public void ZipCreateSmallFiles()
5380
{
@@ -66,4 +93,22 @@ public void ZipCreateSmallFiles()
6693
writer.Write($"file{i}.txt", entryStream);
6794
}
6895
}
96+
97+
[Benchmark(Description = "Zip: Create archive with small files (Async)")]
98+
public async Task ZipCreateSmallFilesAsync()
99+
{
100+
using var outputStream = new MemoryStream();
101+
await using var writer = WriterFactory.OpenAsyncWriter(
102+
outputStream,
103+
ArchiveType.Zip,
104+
new WriterOptions(CompressionType.Deflate) { LeaveStreamOpen = true }
105+
);
106+
107+
for (int i = 0; i < 10; i++)
108+
{
109+
var data = new byte[1024];
110+
using var entryStream = new MemoryStream(data);
111+
await writer.WriteAsync($"file{i}.txt", entryStream).ConfigureAwait(false);
112+
}
113+
}
69114
}

tests/SharpCompress.Performance/Program.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ public static void Main(string[] args)
2020
// Default: Run BenchmarkDotNet
2121
var config = DefaultConfig.Instance.AddJob(
2222
Job.Default.WithToolchain(InProcessEmitToolchain.Instance)
23-
.WithWarmupCount(3) // Minimal warmup iterations for CI
24-
.WithIterationCount(10) // Minimal measurement iterations for CI
25-
.WithInvocationCount(10)
26-
.WithUnrollFactor(1)
23+
.WithWarmupCount(5) // Minimal warmup iterations for CI
24+
.WithIterationCount(30) // Minimal measurement iterations for CI
25+
.WithInvocationCount(30)
26+
.WithUnrollFactor(2)
2727
);
2828

2929
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);

tests/SharpCompress.Performance/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ This project contains performance benchmarks for SharpCompress using [BenchmarkD
55
## Overview
66

77
The benchmarks test all major archive formats supported by SharpCompress:
8-
- **Zip**: Read (Archive & Reader API) and Write operations
9-
- **Tar**: Read (Archive & Reader API) and Write operations, including Tar.GZip
10-
- **Rar**: Read operations (Archive & Reader API)
11-
- **7Zip**: Read operations for LZMA and LZMA2 compression
12-
- **GZip**: Compression and decompression
8+
- **Zip**: Read (Archive & Reader API) and Write operations, each with sync and async variants
9+
- **Tar**: Read (Archive & Reader API) and Write operations, including Tar.GZip, each with sync and async variants
10+
- **Rar**: Read operations (Archive & Reader API), each with sync and async variants
11+
- **7Zip**: Read operations for LZMA and LZMA2 compression, each with sync and async variants
12+
- **GZip**: Compression and decompression, each with sync and async variants
1313

1414
## Running Benchmarks
1515

0 commit comments

Comments
 (0)