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 } }