diff --git a/src/System.IO.Compression/System.IO.Compression.sln b/src/System.IO.Compression/System.IO.Compression.sln
index 019fda56a631..74f9740f89d4 100644
--- a/src/System.IO.Compression/System.IO.Compression.sln
+++ b/src/System.IO.Compression/System.IO.Compression.sln
@@ -45,4 +45,7 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(CodealikeProperties) = postSolution
+ SolutionGuid = a7e376ea-0587-47fe-88c2-fd75be4b20bb
+ EndGlobalSection
EndGlobal
diff --git a/src/System.IO.Compression/src/System.IO.Compression.csproj b/src/System.IO.Compression/src/System.IO.Compression.csproj
index ca38945ebaf6..45f7d13b36b5 100644
--- a/src/System.IO.Compression/src/System.IO.Compression.csproj
+++ b/src/System.IO.Compression/src/System.IO.Compression.csproj
@@ -48,6 +48,7 @@
+
diff --git a/src/System.IO.Compression/src/System/IO/Compression/DeflateStream.cs b/src/System.IO.Compression/src/System/IO/Compression/DeflateStream.cs
index ac47f8dd2a21..4e1e0d56756c 100644
--- a/src/System.IO.Compression/src/System/IO/Compression/DeflateStream.cs
+++ b/src/System.IO.Compression/src/System/IO/Compression/DeflateStream.cs
@@ -5,11 +5,14 @@
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics.Contracts;
+using System.IO.Compression.Internal;
namespace System.IO.Compression
{
public partial class DeflateStream : Stream
{
+ private static readonly ObjectPool s_byteBufferPool = new ObjectPool(() => new byte[DefaultBufferSize], 32);
+
internal const int DefaultBufferSize = 8192;
private Stream _stream;
@@ -17,7 +20,7 @@ public partial class DeflateStream : Stream
private bool _leaveOpen;
private IInflater _inflater;
private IDeflater _deflater;
- private byte[] _buffer;
+ private readonly byte[] _buffer;
private int _asyncOperations;
@@ -50,8 +53,8 @@ internal DeflateStream(Stream stream, bool leaveOpen, IFileFormatReader reader)
_inflater = CreateInflater(reader);
_stream = stream;
_mode = CompressionMode.Decompress;
- _leaveOpen = leaveOpen;
- _buffer = new byte[DefaultBufferSize];
+ _leaveOpen = leaveOpen;
+ _buffer = s_byteBufferPool.Allocate();
}
@@ -85,7 +88,7 @@ public DeflateStream(Stream stream, CompressionMode mode, bool leaveOpen)
_stream = stream;
_mode = mode;
_leaveOpen = leaveOpen;
- _buffer = new byte[DefaultBufferSize];
+ _buffer = s_byteBufferPool.Allocate();
}
// Implies mode = Compress
@@ -115,7 +118,7 @@ public DeflateStream(Stream stream, CompressionLevel compressionLevel, bool leav
_deflater = CreateDeflater(compressionLevel);
- _buffer = new byte[DefaultBufferSize];
+ _buffer = s_byteBufferPool.Allocate();
}
private static IDeflater CreateDeflater(CompressionLevel? compressionLevel)
@@ -611,6 +614,8 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}
} // finally
+
+ s_byteBufferPool.Free(_buffer);
} // finally
} // Dispose
diff --git a/src/System.IO.Compression/src/System/IO/Compression/InflaterManaged.cs b/src/System.IO.Compression/src/System/IO/Compression/InflaterManaged.cs
index 3377e028a918..6fe4bc8dca15 100644
--- a/src/System.IO.Compression/src/System/IO/Compression/InflaterManaged.cs
+++ b/src/System.IO.Compression/src/System/IO/Compression/InflaterManaged.cs
@@ -743,7 +743,10 @@ private bool DecodeDynamicBlockHeader()
return true;
}
- public void Dispose() { }
+ public void Dispose()
+ {
+ _output.Dispose();
+ }
}
}
diff --git a/src/System.IO.Compression/src/System/IO/Compression/Internal/ObjectPool`1.cs b/src/System.IO.Compression/src/System/IO/Compression/Internal/ObjectPool`1.cs
new file mode 100644
index 000000000000..33e213125438
--- /dev/null
+++ b/src/System.IO.Compression/src/System/IO/Compression/Internal/ObjectPool`1.cs
@@ -0,0 +1,115 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Threading;
+
+namespace System.IO.Compression.Internal
+{
+ ///
+ /// Generic implementation of object pooling pattern with predefined pool size limit. The main
+ /// purpose is that limited number of frequently used objects can be kept in the pool for
+ /// further recycling.
+ ///
+ /// Notes:
+ /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there
+ /// is no space in the pool, extra returned objects will be dropped.
+ ///
+ /// 2) it is implied that if object was obtained from a pool, the caller will return it back in
+ /// a relatively short time. Keeping checked out objects for long durations is ok, but
+ /// reduces usefulness of pooling. Just new up your own.
+ ///
+ /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
+ /// Rationale:
+ /// If there is no intent for reusing the object, do not use pool - just use "new".
+ ///
+ internal sealed class ObjectPool where T : class
+ {
+ private struct Element
+ {
+ internal T Value;
+ }
+
+ // storage for the pool objects.
+ private readonly Element[] _items;
+
+ // factory is stored for the lifetime of the pool. We will call this only when pool needs to
+ // expand. compared to "new T()", Func gives more flexibility to implementers and faster
+ // than "new T()".
+ private readonly Func _factory;
+
+
+ internal ObjectPool(Func factory)
+ : this(factory, Environment.ProcessorCount * 2)
+ { }
+
+ internal ObjectPool(Func factory, int size)
+ {
+ _factory = factory;
+ _items = new Element[size];
+ }
+
+ private T CreateInstance()
+ {
+ var inst = _factory();
+ return inst;
+ }
+
+ ///
+ /// Produces an instance.
+ ///
+ ///
+ /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
+ /// Note that Free will try to store recycled objects close to the start thus statistically
+ /// reducing how far we will typically search.
+ ///
+ internal T Allocate()
+ {
+ var items = _items;
+ T inst;
+
+ for (int i = 0; i < items.Length; i++)
+ {
+ // Note that the read is optimistically not synchronized. That is intentional.
+ // We will interlock only when we have a candidate. in a worst case we may miss some
+ // recently returned objects. Not a big deal.
+ inst = items[i].Value;
+ if (inst != null)
+ {
+ if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst))
+ {
+ goto gotInstance;
+ }
+ }
+ }
+
+ inst = CreateInstance();
+ gotInstance:
+
+ return inst;
+ }
+
+ ///
+ /// Returns objects to the pool.
+ ///
+ ///
+ /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
+ /// Note that Free will try to store recycled objects close to the start thus statistically
+ /// reducing how far we will typically search in Allocate.
+ ///
+ internal void Free(T obj)
+ {
+ var items = _items;
+ for (int i = 0; i < items.Length; i++)
+ {
+ if (items[i].Value == null)
+ {
+ // Intentionally not using interlocked here.
+ // In a worst case scenario two objects may be stored into same slot.
+ // It is very unlikely to happen and will only mean that one of the objects will get collected.
+ items[i].Value = obj;
+ break;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.IO.Compression/src/System/IO/Compression/OutputWindow.cs b/src/System.IO.Compression/src/System/IO/Compression/OutputWindow.cs
index 544fe9d2833c..75258eea969f 100644
--- a/src/System.IO.Compression/src/System/IO/Compression/OutputWindow.cs
+++ b/src/System.IO.Compression/src/System/IO/Compression/OutputWindow.cs
@@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.Globalization;
+using System.IO.Compression.Internal;
namespace System.IO.Compression
{
@@ -13,15 +14,22 @@ namespace System.IO.Compression
// we need to look back in the output window and copy bytes from there.
// We use a byte array of WindowSize circularly.
//
- internal class OutputWindow
+ internal class OutputWindow : IDisposable
{
+ private static readonly ObjectPool s_byteBufferPool = new ObjectPool(() => new byte[WindowSize], 16);
+
private const int WindowSize = 32768;
private const int WindowMask = 32767;
- private byte[] _window = new byte[WindowSize]; //The window is 2^15 bytes
+ private readonly byte[] _window; //The window is 2^15 bytes
private int _end; // this is the position to where we should write next byte
private int _bytesUsed; // The number of bytes in the output window which is not consumed.
+ public OutputWindow()
+ {
+ _window = s_byteBufferPool.Allocate();
+ }
+
// Add a byte to output window
public void Write(byte b)
{
@@ -150,5 +158,31 @@ public int CopyTo(byte[] output, int offset, int length)
Debug.Assert(_bytesUsed >= 0, "check this function and find why we copied more bytes than we have");
return copied;
}
+
+ #region IDisposable Support
+
+ private bool disposedValue = false; // To detect redundant calls
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ s_byteBufferPool.Free(_window);
+ }
+
+ disposedValue = true;
+ }
+ }
+
+ // This code added to correctly implement the disposable pattern.
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(true);
+ }
+
+ #endregion
}
}