diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/ICSharpCode.SharpZipLib.Benchmark.csproj b/benchmark/ICSharpCode.SharpZipLib.Benchmark/ICSharpCode.SharpZipLib.Benchmark.csproj
index 7688d0ff2..7fa26f80f 100644
--- a/benchmark/ICSharpCode.SharpZipLib.Benchmark/ICSharpCode.SharpZipLib.Benchmark.csproj
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/ICSharpCode.SharpZipLib.Benchmark.csproj
@@ -1,18 +1,16 @@
-
- Exe
- net6.0;netcoreapp3.1;net462
-
+
+ Exe
+ net462;net6.0
+
-
- 0.12.1
-
+
-
+
diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Program.cs b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Program.cs
index 697e2923a..3a7beebbb 100644
--- a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Program.cs
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Program.cs
@@ -9,9 +9,8 @@ public class MultipleRuntimes : ManualConfig
{
public MultipleRuntimes()
{
- AddJob(Job.Default.WithToolchain(CsProjClassicNetToolchain.Net461).AsBaseline()); // NET 4.6.1
- AddJob(Job.Default.WithToolchain(CsProjCoreToolchain.NetCoreApp21)); // .NET Core 2.1
- AddJob(Job.Default.WithToolchain(CsProjCoreToolchain.NetCoreApp31)); // .NET Core 3.1
+ AddJob(Job.Default.WithToolchain(CsProjClassicNetToolchain.Net462).AsBaseline()); // NET 4.6.2
+ AddJob(Job.Default.WithToolchain(CsProjCoreToolchain.NetCoreApp60)); // .NET 6.0
}
}
diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipFile.cs b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipFile.cs
new file mode 100644
index 000000000..0a84e0b88
--- /dev/null
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipFile.cs
@@ -0,0 +1,63 @@
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using ICSharpCode.SharpZipLib.Zip;
+
+namespace ICSharpCode.SharpZipLib.Benchmark.Zip
+{
+ [MemoryDiagnoser]
+ [Config(typeof(MultipleRuntimes))]
+ public class ZipFile
+ {
+ private readonly byte[] readBuffer = new byte[4096];
+ private string zipFileWithLargeAmountOfEntriesPath;
+
+ [GlobalSetup]
+ public async Task GlobalSetup()
+ {
+ SharpZipLibOptions.InflaterPoolSize = 4;
+
+ // large real-world test file from test262 repository
+ string commitSha = "2e4e0e6b8ebe3348a207144204cb6d7a5571c863";
+ zipFileWithLargeAmountOfEntriesPath = Path.Combine(Path.GetTempPath(), $"{commitSha}.zip");
+ if (!File.Exists(zipFileWithLargeAmountOfEntriesPath))
+ {
+ var uri = $"https://github.com/tc39/test262/archive/{commitSha}.zip";
+
+ Console.WriteLine("Loading test262 repository archive from {0}", uri);
+
+ using (var client = new HttpClient())
+ {
+ using (var downloadStream = await client.GetStreamAsync(uri))
+ {
+ using (var writeStream = File.OpenWrite(zipFileWithLargeAmountOfEntriesPath))
+ {
+ await downloadStream.CopyToAsync(writeStream);
+ Console.WriteLine("File downloaded and saved to {0}", zipFileWithLargeAmountOfEntriesPath);
+ }
+ }
+ }
+ }
+
+ }
+
+ [Benchmark]
+ public void ReadLargeZipFile()
+ {
+ using (var file = new SharpZipLib.Zip.ZipFile(zipFileWithLargeAmountOfEntriesPath))
+ {
+ foreach (ZipEntry entry in file)
+ {
+ using (var stream = file.GetInputStream(entry))
+ {
+ while (stream.Read(readBuffer, 0, readBuffer.Length) > 0)
+ {
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipInputStream.cs b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipInputStream.cs
index eb099ebfd..2e0c057d8 100644
--- a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipInputStream.cs
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipInputStream.cs
@@ -1,5 +1,4 @@
-using System;
-using System.IO;
+using System.IO;
using BenchmarkDotNet.Attributes;
namespace ICSharpCode.SharpZipLib.Benchmark.Zip
@@ -15,7 +14,8 @@ public class ZipInputStream
byte[] zippedData;
byte[] readBuffer = new byte[4096];
- public ZipInputStream()
+ [GlobalSetup]
+ public void GlobalSetup()
{
using (var memoryStream = new MemoryStream())
{
diff --git a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipOutputStream.cs b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipOutputStream.cs
index ed125c1c7..c4e8620e3 100644
--- a/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipOutputStream.cs
+++ b/benchmark/ICSharpCode.SharpZipLib.Benchmark/Zip/ZipOutputStream.cs
@@ -1,5 +1,4 @@
-using System;
-using System.IO;
+using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
@@ -16,7 +15,8 @@ public class ZipOutputStream
byte[] outputBuffer;
byte[] inputBuffer;
- public ZipOutputStream()
+ [GlobalSetup]
+ public void GlobalSetup()
{
inputBuffer = new byte[ChunkSize];
outputBuffer = new byte[N];
diff --git a/src/ICSharpCode.SharpZipLib/Core/InflaterPool.cs b/src/ICSharpCode.SharpZipLib/Core/InflaterPool.cs
new file mode 100644
index 000000000..39db32e8c
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Core/InflaterPool.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Concurrent;
+using ICSharpCode.SharpZipLib.Zip.Compression;
+
+namespace ICSharpCode.SharpZipLib.Core
+{
+ ///
+ /// Pool for instances as they can be costly due to byte array allocations.
+ ///
+ internal sealed class InflaterPool
+ {
+ private readonly ConcurrentQueue noHeaderPool = new ConcurrentQueue();
+ private readonly ConcurrentQueue headerPool = new ConcurrentQueue();
+
+ internal static InflaterPool Instance { get; } = new InflaterPool();
+
+ private InflaterPool()
+ {
+ }
+
+ internal Inflater Rent(bool noHeader = false)
+ {
+ if (SharpZipLibOptions.InflaterPoolSize <= 0)
+ {
+ return new Inflater(noHeader);
+ }
+
+ var pool = GetPool(noHeader);
+
+ PooledInflater inf;
+ if (pool.TryDequeue(out var inflater))
+ {
+ inf = inflater;
+ inf.Reset();
+ }
+ else
+ {
+ inf = new PooledInflater(noHeader);
+ }
+
+ return inf;
+ }
+
+ internal void Return(Inflater inflater)
+ {
+ if (SharpZipLibOptions.InflaterPoolSize <= 0)
+ {
+ return;
+ }
+
+ if (!(inflater is PooledInflater pooledInflater))
+ {
+ throw new ArgumentException("Returned inflater was not a pooled one");
+ }
+
+ var pool = GetPool(inflater.noHeader);
+ if (pool.Count < SharpZipLibOptions.InflaterPoolSize)
+ {
+ pooledInflater.Reset();
+ pool.Enqueue(pooledInflater);
+ }
+ }
+
+ private ConcurrentQueue GetPool(bool noHeader) => noHeader ? noHeaderPool : headerPool;
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs b/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs
index db5aef2da..feca66b3d 100644
--- a/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/GZip/GzipInputStream.cs
@@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Text;
+using ICSharpCode.SharpZipLib.Core;
namespace ICSharpCode.SharpZipLib.GZip
{
@@ -82,7 +83,7 @@ public GZipInputStream(Stream baseInputStream)
/// Size of the buffer to use
///
public GZipInputStream(Stream baseInputStream, int size)
- : base(baseInputStream, new Inflater(true), size)
+ : base(baseInputStream, InflaterPool.Instance.Rent(true), size)
{
}
diff --git a/src/ICSharpCode.SharpZipLib/SharpZipLibOptions.cs b/src/ICSharpCode.SharpZipLib/SharpZipLibOptions.cs
new file mode 100644
index 000000000..a6694e71e
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/SharpZipLibOptions.cs
@@ -0,0 +1,15 @@
+using ICSharpCode.SharpZipLib.Zip.Compression;
+
+namespace ICSharpCode.SharpZipLib
+{
+ ///
+ /// Global options to alter behavior.
+ ///
+ public static class SharpZipLibOptions
+ {
+ ///
+ /// The max pool size allowed for reusing instances, defaults to 0 (disabled).
+ ///
+ public static int InflaterPoolSize { get; set; } = 0;
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/Inflater.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/Inflater.cs
index 439b4c601..5bf2a985e 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/Compression/Inflater.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/Inflater.cs
@@ -137,7 +137,7 @@ public class Inflater
/// True means, that the inflated stream doesn't contain a Zlib header or
/// footer.
///
- private bool noHeader;
+ internal bool noHeader;
private readonly StreamManipulator input;
private OutputWindow outputWindow;
diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/PooledInflater.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/PooledInflater.cs
new file mode 100644
index 000000000..0828de3ef
--- /dev/null
+++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/PooledInflater.cs
@@ -0,0 +1,14 @@
+using ICSharpCode.SharpZipLib.Core;
+
+namespace ICSharpCode.SharpZipLib.Zip.Compression
+{
+ ///
+ /// A marker type for pooled version of an inflator that we can return back to .
+ ///
+ internal sealed class PooledInflater : Inflater
+ {
+ public PooledInflater(bool noHeader) : base(noHeader)
+ {
+ }
+ }
+}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
index 7790474d2..980ffc701 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/InflaterInputStream.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Security.Cryptography;
+using ICSharpCode.SharpZipLib.Core;
namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams
{
@@ -339,7 +340,7 @@ public class InflaterInputStream : Stream
/// The InputStream to read bytes from
///
public InflaterInputStream(Stream baseInputStream)
- : this(baseInputStream, new Inflater(), 4096)
+ : this(baseInputStream, InflaterPool.Instance.Rent(), 4096)
{
}
@@ -630,6 +631,12 @@ protected override void Dispose(bool disposing)
baseInputStream.Dispose();
}
}
+
+ if (inf is PooledInflater inflater)
+ {
+ InflaterPool.Instance.Return(inflater);
+ }
+ inf = null;
}
///
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs
index 951cc6e20..7fc1c5592 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs
@@ -314,7 +314,7 @@ public enum FileUpdateMode
/// }
///
///
- public class ZipFile : IEnumerable, IDisposable
+ public class ZipFile : IEnumerable, IDisposable
{
#region KeyHandling
@@ -810,7 +810,31 @@ public StringCodec StringCodec
///
/// The Zip file has been closed.
///
- public IEnumerator GetEnumerator()
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// Gets an enumerator for the Zip entries in this Zip file.
+ ///
+ /// Returns an for this archive.
+ ///
+ /// The Zip file has been closed.
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ ///
+ /// Gets an enumerator for the Zip entries in this Zip file.
+ ///
+ /// Returns an for this archive.
+ ///
+ /// The Zip file has been closed.
+ ///
+ public ZipEntryEnumerator GetEnumerator()
{
if (isDisposed_)
{
@@ -954,7 +978,7 @@ public Stream GetInputStream(long entryIndex)
case CompressionMethod.Deflated:
// No need to worry about ownership and closing as underlying stream close does nothing.
- result = new InflaterInputStream(result, new Inflater(true));
+ result = new InflaterInputStream(result, InflaterPool.Instance.Rent(true));
break;
case CompressionMethod.BZip2:
@@ -4001,20 +4025,26 @@ public static implicit operator string(ZipString zipString)
///
/// An enumerator for Zip entries
///
- private class ZipEntryEnumerator : IEnumerator
+ public struct ZipEntryEnumerator : IEnumerator
{
#region Constructors
+ ///
+ /// Constructs a new instance of .
+ ///
+ /// Entries to iterate.
public ZipEntryEnumerator(ZipEntry[] entries)
{
array = entries;
+ index = -1;
}
#endregion Constructors
#region IEnumerator Members
- public object Current
+ ///
+ public ZipEntry Current
{
get
{
@@ -4022,22 +4052,32 @@ public object Current
}
}
+ ///
+ object IEnumerator.Current => Current;
+
+ ///
public void Reset()
{
index = -1;
}
+ ///
public bool MoveNext()
{
return (++index < array.Length);
}
+ ///
+ public void Dispose()
+ {
+ }
+
#endregion IEnumerator Members
#region Instance Fields
private ZipEntry[] array;
- private int index = -1;
+ private int index;
#endregion Instance Fields
}
diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
index 4d258afc8..37e9e8ba8 100644
--- a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
+++ b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs
@@ -1,10 +1,11 @@
using ICSharpCode.SharpZipLib.Checksum;
using ICSharpCode.SharpZipLib.Encryption;
-using ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System;
using System.Diagnostics;
using System.IO;
+using ICSharpCode.SharpZipLib.Core;
+using ICSharpCode.SharpZipLib.Zip.Compression;
namespace ICSharpCode.SharpZipLib.Zip
{
@@ -88,7 +89,7 @@ public class ZipInputStream : InflaterInputStream
///
/// The underlying providing data.
public ZipInputStream(Stream baseInputStream)
- : base(baseInputStream, new Inflater(true))
+ : base(baseInputStream, InflaterPool.Instance.Rent(true))
{
internalReader = new ReadDataHandler(ReadingNotAvailable);
}
@@ -99,7 +100,7 @@ public ZipInputStream(Stream baseInputStream)
/// The underlying providing data.
/// Size of the buffer.
public ZipInputStream(Stream baseInputStream, int bufferSize)
- : base(baseInputStream, new Inflater(true), bufferSize)
+ : base(baseInputStream, InflaterPool.Instance.Rent(true), bufferSize)
{
internalReader = new ReadDataHandler(ReadingNotAvailable);
}