Memorify DeflateManagedStream in System.IO.Compression (#48085)
authorNewell Clark <newellofthefuture@gmail.com>
Sun, 14 Feb 2021 10:13:42 +0000 (05:13 -0500)
committerGitHub <noreply@github.com>
Sun, 14 Feb 2021 10:13:42 +0000 (02:13 -0800)
src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/DeflateManagedStream.cs
src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/InflaterManaged.cs
src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/InputBuffer.cs
src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateManaged/OutputWindow.cs

index 2d39852..f24e15d 100644 (file)
@@ -156,60 +156,50 @@ namespace System.IO.Compression
         public override int EndRead(IAsyncResult asyncResult) =>
             TaskToApm.End<int>(asyncResult);
 
-        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        private ValueTask<int> ReadAsyncInternal(Memory<byte> buffer, CancellationToken cancellationToken)
         {
-            // We use this checking order for compat to earlier versions:
-            if (_asyncOperations != 0)
-                throw new InvalidOperationException(SR.InvalidBeginCall);
-
-            ValidateBufferArguments(buffer, offset, count);
-            EnsureNotDisposed();
-
             if (cancellationToken.IsCancellationRequested)
             {
-                return Task.FromCanceled<int>(cancellationToken);
+                return ValueTask.FromCanceled<int>(cancellationToken);
             }
 
             Interlocked.Increment(ref _asyncOperations);
-            Task<int>? readTask = null;
+            bool startedAsyncWork = false;
 
             try
             {
                 // Try to read decompressed data in output buffer
-                int bytesRead = _inflater.Inflate(buffer, offset, count);
+                int bytesRead = _inflater.Inflate(buffer);
                 if (bytesRead != 0)
                 {
                     // If decompression output buffer is not empty, return immediately.
-                    return Task.FromResult(bytesRead);
+                    return ValueTask.FromResult(bytesRead);
                 }
 
                 if (_inflater.Finished())
                 {
                     // end of compression stream
-                    return Task.FromResult(0);
+                    return ValueTask.FromResult(0);
                 }
 
                 // If there is no data on the output buffer and we are not at
                 // the end of the stream, we need to get more data from the base stream
-                readTask = _stream!.ReadAsync(_buffer, 0, _buffer.Length, cancellationToken);
-                if (readTask == null)
-                {
-                    throw new InvalidOperationException(SR.NotSupported_UnreadableStream);
-                }
+                ValueTask<int> readTask = _stream!.ReadAsync(_buffer.AsMemory(), cancellationToken);
+                startedAsyncWork = true;
 
-                return ReadAsyncCore(readTask, buffer, offset, count, cancellationToken);
+                return ReadAsyncCore(readTask, buffer, cancellationToken);
             }
             finally
             {
                 // if we haven't started any async work, decrement the counter to end the transaction
-                if (readTask == null)
+                if (!startedAsyncWork)
                 {
                     Interlocked.Decrement(ref _asyncOperations);
                 }
             }
         }
 
-        private async Task<int> ReadAsyncCore(Task<int> readTask, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        private async ValueTask<int> ReadAsyncCore(ValueTask<int> readTask, Memory<byte> buffer, CancellationToken cancellationToken)
         {
             try
             {
@@ -234,17 +224,13 @@ namespace System.IO.Compression
 
                     // Feed the data from base stream into decompression engine
                     _inflater.SetInput(_buffer, 0, bytesRead);
-                    bytesRead = _inflater.Inflate(buffer, offset, count);
+                    bytesRead = _inflater.Inflate(buffer);
 
                     if (bytesRead == 0 && !_inflater.Finished())
                     {
                         // We could have read in head information and didn't get any data.
                         // Read from the base stream again.
-                        readTask = _stream!.ReadAsync(_buffer, 0, _buffer.Length, cancellationToken);
-                        if (readTask == null)
-                        {
-                            throw new InvalidOperationException(SR.NotSupported_UnreadableStream);
-                        }
+                        readTask = _stream!.ReadAsync(_buffer.AsMemory(), cancellationToken);
                     }
                     else
                     {
@@ -258,6 +244,29 @@ namespace System.IO.Compression
             }
         }
 
+        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            // We use this checking order for compat to earlier versions:
+            if (_asyncOperations != 0)
+                throw new InvalidOperationException(SR.InvalidBeginCall);
+
+            ValidateBufferArguments(buffer, offset, count);
+            EnsureNotDisposed();
+
+            return ReadAsyncInternal(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+        }
+
+        public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            // We use this checking order for compat to earlier versions:
+            if (_asyncOperations != 0)
+                throw new InvalidOperationException(SR.InvalidBeginCall);
+
+            EnsureNotDisposed();
+
+            return ReadAsyncInternal(buffer, cancellationToken);
+        }
+
         public override void Write(byte[] buffer, int offset, int count)
         {
             throw new InvalidOperationException(SR.CannotWriteToDeflateStream);
index 7fe90bb..8372368 100644 (file)
@@ -86,6 +86,8 @@ namespace System.IO.Compression
             _state = InflaterState.ReadingBFinal; // start by reading BFinal bit
         }
 
+        public void SetInput(Memory<byte> inputBytes) => _input.SetInput(inputBytes);
+
         public void SetInput(byte[] inputBytes, int offset, int length) =>
             _input.SetInput(inputBytes, offset, length); // append the bytes
 
@@ -93,7 +95,7 @@ namespace System.IO.Compression
 
         public int AvailableOutput => _output.AvailableBytes;
 
-        public int Inflate(byte[] bytes, int offset, int length)
+        public int Inflate(Memory<byte> bytes)
         {
             // copy bytes from output to outputbytes if we have available bytes
             // if buffer is not filled up. keep decoding until no input are available
@@ -104,14 +106,14 @@ namespace System.IO.Compression
                 int copied = 0;
                 if (_uncompressedSize == -1)
                 {
-                    copied = _output.CopyTo(bytes, offset, length);
+                    copied = _output.CopyTo(bytes);
                 }
                 else
                 {
                     if (_uncompressedSize > _currentInflatedCount)
                     {
-                        length = (int)Math.Min(length, _uncompressedSize - _currentInflatedCount);
-                        copied = _output.CopyTo(bytes, offset, length);
+                        bytes = bytes.Slice(0, (int)Math.Min(bytes.Length, _uncompressedSize - _currentInflatedCount));
+                        copied = _output.CopyTo(bytes);
                         _currentInflatedCount += copied;
                     }
                     else
@@ -122,13 +124,13 @@ namespace System.IO.Compression
                 }
                 if (copied > 0)
                 {
-                    offset += copied;
+                    bytes = bytes.Slice(copied, bytes.Length - copied);
                     count += copied;
-                    length -= copied;
                 }
 
-                if (length == 0)
-                {   // filled in the bytes array
+                if (bytes.IsEmpty)
+                {
+                    // filled in the bytes buffer
                     break;
                 }
                 // Decode will return false when more input is needed
@@ -137,6 +139,8 @@ namespace System.IO.Compression
             return count;
         }
 
+        public int Inflate(byte[] bytes, int offset, int length) => Inflate(bytes.AsMemory(offset, length));
+
         //Each block of compressed data begins with 3 header bits
         // containing the following data:
         //    first bit       BFINAL
index 7b5d292..42e3e66 100644 (file)
@@ -17,9 +17,7 @@ namespace System.IO.Compression
 
     internal sealed class InputBuffer
     {
-        private byte[]? _buffer;           // byte array to store input
-        private int _start;               // start poisition of the buffer
-        private int _end;                 // end position of the buffer
+        private Memory<byte> _buffer;     // memory to store input
         private uint _bitBuffer;      // store the bits here, we can quickly shift in this buffer
         private int _bitsInBuffer;    // number of bits available in bitBuffer
 
@@ -27,7 +25,7 @@ namespace System.IO.Compression
         public int AvailableBits => _bitsInBuffer;
 
         /// <summary>Total bytes available in the input buffer.</summary>
-        public int AvailableBytes => (_end - _start) + (_bitsInBuffer / 8);
+        public int AvailableBytes => _buffer.Length + (_bitsInBuffer / 8);
 
         /// <summary>Ensure that count bits are in the bit buffer.</summary>
         /// <param name="count">Can be up to 16.</param>
@@ -43,9 +41,10 @@ namespace System.IO.Compression
                 {
                     return false;
                 }
-                Debug.Assert(_buffer != null);
+
                 // insert a byte to bitbuffer
-                _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer;
+                _bitBuffer |= (uint)_buffer.Span[0] << _bitsInBuffer;
+                _buffer = _buffer.Slice(1);
                 _bitsInBuffer += 8;
 
                 if (_bitsInBuffer < count)
@@ -55,7 +54,8 @@ namespace System.IO.Compression
                         return false;
                     }
                     // insert a byte to bitbuffer
-                    _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer;
+                    _bitBuffer |= (uint)_buffer.Span[0] << _bitsInBuffer;
+                    _buffer = _buffer.Slice(1);
                     _bitsInBuffer += 8;
                 }
             }
@@ -72,26 +72,29 @@ namespace System.IO.Compression
         /// </summary>
         public uint TryLoad16Bits()
         {
-            Debug.Assert(_buffer != null);
             if (_bitsInBuffer < 8)
             {
-                if (_start < _end)
+                if (_buffer.Length > 1)
                 {
-                    _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer;
-                    _bitsInBuffer += 8;
+                    Span<byte> span = _buffer.Span;
+                    _bitBuffer |= (uint)span[0] << _bitsInBuffer;
+                    _bitBuffer |= (uint)span[1] << (_bitsInBuffer + 8);
+                    _buffer = _buffer.Slice(2);
+                    _bitsInBuffer += 16;
                 }
-
-                if (_start < _end)
+                else if (_buffer.Length != 0)
                 {
-                    _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer;
+                    _bitBuffer |= (uint)_buffer.Span[0] << _bitsInBuffer;
+                    _buffer = _buffer.Slice(1);
                     _bitsInBuffer += 8;
                 }
             }
             else if (_bitsInBuffer < 16)
             {
-                if (_start < _end)
+                if (!_buffer.IsEmpty)
                 {
-                    _bitBuffer |= (uint)_buffer[_start++] << _bitsInBuffer;
+                    _bitBuffer |= (uint)_buffer.Span[0] << _bitsInBuffer;
+                    _buffer = _buffer.Slice(1);
                     _bitsInBuffer += 8;
                 }
             }
@@ -118,52 +121,74 @@ namespace System.IO.Compression
         }
 
         /// <summary>
-        /// Copies length bytes from input buffer to output buffer starting at output[offset].
+        /// Copies bytes from input buffer to output buffer.
         /// You have to make sure, that the buffer is byte aligned. If not enough bytes are
         /// available, copies fewer bytes.
         /// </summary>
         /// <returns>Returns the number of bytes copied, 0 if no byte is available.</returns>
-        public int CopyTo(byte[] output, int offset, int length)
+        public int CopyTo(Memory<byte> output)
         {
-            Debug.Assert(output != null);
-            Debug.Assert(offset >= 0);
-            Debug.Assert(length >= 0);
-            Debug.Assert(offset <= output.Length - length);
-            Debug.Assert((_bitsInBuffer % 8) == 0);
+            Debug.Assert(_bitsInBuffer % 8 == 0);
 
             // Copy the bytes in bitBuffer first.
             int bytesFromBitBuffer = 0;
-            while (_bitsInBuffer > 0 && length > 0)
+            while (_bitsInBuffer > 0 && !output.IsEmpty)
             {
-                output[offset++] = (byte)_bitBuffer;
+                output.Span[0] = (byte)_bitBuffer;
+                output = output.Slice(1);
                 _bitBuffer >>= 8;
                 _bitsInBuffer -= 8;
-                length--;
                 bytesFromBitBuffer++;
             }
 
-            if (length == 0)
+            if (output.IsEmpty)
             {
                 return bytesFromBitBuffer;
             }
 
-            int avail = _end - _start;
-            if (length > avail)
-            {
-                length = avail;
-            }
-
-            Debug.Assert(_buffer != null);
-            Array.Copy(_buffer, _start, output, offset, length);
-            _start += length;
+            int length = Math.Min(output.Length, _buffer.Length);
+            _buffer.Slice(0, length).CopyTo(output);
+            _buffer = _buffer.Slice(length);
             return bytesFromBitBuffer + length;
         }
 
         /// <summary>
+        /// Copies length bytes from input buffer to output buffer starting at output[offset].
+        /// You have to make sure, that the buffer is byte aligned. If not enough bytes are
+        /// available, copies fewer bytes.
+        /// </summary>
+        /// <returns>Returns the number of bytes copied, 0 if no byte is available.</returns>
+        public int CopyTo(byte[] output, int offset, int length)
+        {
+            Debug.Assert(output != null);
+            Debug.Assert(offset >= 0);
+            Debug.Assert(length >= 0);
+            Debug.Assert(offset <= output.Length - length);
+            Debug.Assert((_bitsInBuffer % 8) == 0);
+
+            return CopyTo(output.AsMemory(offset, length));
+        }
+
+        /// <summary>
         /// Return true is all input bytes are used.
         /// This means the caller can call SetInput to add more input.
         /// </summary>
-        public bool NeedsInput() => _start == _end;
+        public bool NeedsInput() => _buffer.IsEmpty;
+
+        /// <summary>
+        /// Set the byte buffer to be processed.
+        /// All the bits remained in bitbuffer will be processed before the new bytes.
+        /// We don't clone the byte buffer here since it is expensive.
+        /// The caller should make sure after a buffer is passed in, that
+        /// it will not be changed before calling this function again.
+        /// </summary>
+        public void SetInput(Memory<byte> buffer)
+        {
+            if (_buffer.IsEmpty)
+            {
+                _buffer = buffer;
+            }
+        }
 
         /// <summary>
         /// Set the byte array to be processed.
@@ -179,12 +204,7 @@ namespace System.IO.Compression
             Debug.Assert(length >= 0);
             Debug.Assert(offset <= buffer.Length - length);
 
-            if (_start == _end)
-            {
-                _buffer = buffer;
-                _start = offset;
-                _end = offset + length;
-            }
+            SetInput(buffer.AsMemory(offset, length));
         }
 
         /// <summary>Skip n bits in the buffer.</summary>
index 949e279..2cdcf7d 100644 (file)
@@ -117,38 +117,42 @@ namespace System.IO.Compression
         /// <summary>Bytes not consumed in output window.</summary>
         public int AvailableBytes => _bytesUsed;
 
-        /// <summary>Copy the decompressed bytes to output array.</summary>
-        public int CopyTo(byte[] output, int offset, int length)
+        /// <summary>Copy the decompressed bytes to output buffer.</summary>
+        public int CopyTo(Memory<byte> output)
         {
             int copy_end;
 
-            if (length > _bytesUsed)
+            if (output.Length > _bytesUsed)
             {
                 // we can copy all the decompressed bytes out
                 copy_end = _end;
-                length = _bytesUsed;
+                output = output.Slice(0, _bytesUsed);
             }
             else
             {
-                copy_end = (_end - _bytesUsed + length) & WindowMask; // copy length of bytes
+                copy_end = (_end - _bytesUsed + output.Length) & WindowMask; // copy length of bytes
             }
 
-            int copied = length;
+            int copied = output.Length;
 
-            int tailLen = length - copy_end;
+            int tailLen = output.Length - copy_end;
             if (tailLen > 0)
             {
                 // this means we need to copy two parts separately
-                // copy tailLen bytes from the end of output window
-                Array.Copy(_window, WindowSize - tailLen,
-                                  output, offset, tailLen);
-                offset += tailLen;
-                length = copy_end;
+                // copy the taillen bytes from the end of the output window
+                _window.AsSpan(WindowSize - tailLen, tailLen).CopyTo(output.Span);
+                output = output.Slice(tailLen, copy_end);
             }
-            Array.Copy(_window, copy_end - length, output, offset, length);
+            _window.AsSpan(copy_end - output.Length, output.Length).CopyTo(output.Span);
             _bytesUsed -= copied;
             Debug.Assert(_bytesUsed >= 0, "check this function and find why we copied more bytes than we have");
             return copied;
         }
+
+        /// <summary>Copy the decompressed bytes to output array.</summary>
+        public int CopyTo(byte[] output, int offset, int length)
+        {
+            return CopyTo(output.AsMemory(offset, length));
+        }
     }
 }