diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/InflaterDynHeader.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/InflaterDynHeader.cs index 8e0196b11..b71f05b2b 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/Compression/InflaterDynHeader.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/InflaterDynHeader.cs @@ -57,7 +57,7 @@ private IEnumerable CreateStateMachine() while (!input.TryGetBits(3, ref codeLengths, MetaCodeLengthIndex[i])) yield return false; } - var metaCodeTree = new InflaterHuffmanTree(codeLengths); + var metaCodeTree = new InflaterHuffmanTree(codeLengths.AsSpan()); // Decompress the meta tree symbols into the data table code lengths int index = 0; @@ -113,8 +113,8 @@ private IEnumerable CreateStateMachine() if (codeLengths[256] == 0) throw new StreamDecodingException("Inflater dynamic header end-of-block code missing"); - litLenTree = new InflaterHuffmanTree(new ArraySegment(codeLengths, 0, litLenCodeCount)); - distTree = new InflaterHuffmanTree(new ArraySegment(codeLengths, litLenCodeCount, distanceCodeCount)); + litLenTree = new InflaterHuffmanTree(codeLengths.AsSpan(0, litLenCodeCount)); + distTree = new InflaterHuffmanTree(codeLengths.AsSpan(litLenCodeCount, distanceCodeCount)); yield return true; } diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs index ed318824f..e6f2663f4 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/InflaterHuffmanTree.cs @@ -1,6 +1,8 @@ using ICSharpCode.SharpZipLib.Zip.Compression.Streams; using System; using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; namespace ICSharpCode.SharpZipLib.Zip.Compression { @@ -13,6 +15,11 @@ public class InflaterHuffmanTree private const int MAX_BITLEN = 15; + // see InflaterHuffmanTreeTest.GenerateTrees how to generate the sequence + // stored in DLL's static data section so no allocation occurs + private static ReadOnlySpan defLitLenTreeBytes => new byte[] { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8 }; + private static ReadOnlySpan defDistTreeBytes => new byte[] { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 }; + #endregion Constants #region Instance Fields @@ -24,52 +31,41 @@ public class InflaterHuffmanTree /// /// Literal length tree /// - public static InflaterHuffmanTree defLitLenTree; + public static readonly InflaterHuffmanTree defLitLenTree = new InflaterHuffmanTree(defLitLenTreeBytes); /// /// Distance tree /// - public static InflaterHuffmanTree defDistTree; + public static readonly InflaterHuffmanTree defDistTree = new InflaterHuffmanTree(defDistTreeBytes); + + #region Constructors - static InflaterHuffmanTree() + /// + /// Constructs a Huffman tree from the array of code lengths. + /// + /// + /// the array of code lengths + /// + public InflaterHuffmanTree(IList codeLengths) : this(ListToSpan(codeLengths)) { - try - { - byte[] codeLengths = new byte[288]; - int i = 0; - while (i < 144) - { - codeLengths[i++] = 8; - } - while (i < 256) - { - codeLengths[i++] = 9; - } - while (i < 280) - { - codeLengths[i++] = 7; - } - while (i < 288) - { - codeLengths[i++] = 8; - } - defLitLenTree = new InflaterHuffmanTree(codeLengths); + } - codeLengths = new byte[32]; - i = 0; - while (i < 32) - { - codeLengths[i++] = 5; - } - defDistTree = new InflaterHuffmanTree(codeLengths); + private static ReadOnlySpan ListToSpan(IList codeLengths) + { +#if NET6_0_OR_GREATER + if (codeLengths is List list) + { + return CollectionsMarshal.AsSpan(list); } - catch (Exception) +#endif + if (codeLengths is byte[] array) { - throw new SharpZipBaseException("InflaterHuffmanTree: static tree length illegal"); + return array; } - } - #region Constructors + // slow path + return codeLengths.ToArray(); + } /// /// Constructs a Huffman tree from the array of code lengths. @@ -77,21 +73,20 @@ static InflaterHuffmanTree() /// /// the array of code lengths /// - public InflaterHuffmanTree(IList codeLengths) + internal InflaterHuffmanTree(ReadOnlySpan codeLengths) { BuildTree(codeLengths); } #endregion Constructors - private void BuildTree(IList codeLengths) + private void BuildTree(ReadOnlySpan codeLengths) { - int[] blCount = new int[MAX_BITLEN + 1]; - int[] nextCode = new int[MAX_BITLEN + 1]; + Span blCount = stackalloc int[MAX_BITLEN + 1]; + Span nextCode = stackalloc int[MAX_BITLEN + 1]; - for (int i = 0; i < codeLengths.Count; i++) + foreach (var bits in codeLengths) { - int bits = codeLengths[i]; if (bits > 0) { blCount[bits]++; @@ -136,20 +131,21 @@ private void BuildTree(IList codeLengths) } } - for (int i = 0; i < codeLengths.Count; i++) + for (var i = 0; i < codeLengths.Length; i++) { - int bits = codeLengths[i]; + var bits = codeLengths[i]; if (bits == 0) { continue; } + code = nextCode[bits]; int revcode = DeflaterHuffman.BitReverse(code); if (bits <= 9) { do { - tree[revcode] = (short)((i << 4) | bits); + tree[revcode] = (short) ((i << 4) | bits); revcode += 1 << bits; } while (revcode < 512); } @@ -160,10 +156,11 @@ private void BuildTree(IList codeLengths) subTree = -(subTree >> 4); do { - tree[subTree | (revcode >> 9)] = (short)((i << 4) | bits); + tree[subTree | (revcode >> 9)] = (short) ((i << 4) | bits); revcode += 1 << bits; } while (revcode < treeLen); } + nextCode[bits] = code + (1 << (16 - bits)); } } diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/OutputWindow.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/OutputWindow.cs index d8241c18c..b3a5a21be 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/OutputWindow.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/OutputWindow.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace ICSharpCode.SharpZipLib.Zip.Compression.Streams { @@ -36,12 +37,18 @@ public void Write(int value) { if (windowFilled++ == WindowSize) { - throw new InvalidOperationException("Window full"); + ThrowWindowFull(); } window[windowEnd++] = (byte)value; windowEnd &= WindowMask; } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowWindowFull() + { + throw new InvalidOperationException("Window full"); + } + private void SlowRepeat(int repStart, int length, int distance) { while (length-- > 0) diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/InflaterHuffmanTreeTest.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/InflaterHuffmanTreeTest.cs new file mode 100644 index 000000000..9a8553f1f --- /dev/null +++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/InflaterHuffmanTreeTest.cs @@ -0,0 +1,51 @@ +using System; +using NUnit.Framework; + +namespace ICSharpCode.SharpZipLib.Tests.Zip +{ + public class InflaterHuffmanTreeTest + { + /// + /// Generates code based on optimization described in https://github.com/dotnet/csharplang/issues/5295#issue-1028421234 + /// + [Test] + [Explicit] + public void GenerateTrees() + { + // generates the byte arrays needed by InflaterHuffmanTree + var defLitLenTreeBytes = new byte[288]; + int i = 0; + while (i < 144) + { + defLitLenTreeBytes[i++] = 8; + } + + while (i < 256) + { + defLitLenTreeBytes[i++] = 9; + } + + while (i < 280) + { + defLitLenTreeBytes[i++] = 7; + } + + while (i < 288) + { + defLitLenTreeBytes[i++] = 8; + } + + Console.WriteLine($"private static ReadOnlySpan defLitLenTreeBytes => new byte[] {{ { string.Join(", ", defLitLenTreeBytes) } }};"); + + + var defDistTreeBytes = new byte[32]; + i = 0; + while (i < 32) + { + defDistTreeBytes[i++] = 5; + } + + Console.WriteLine($"private static ReadOnlySpan defDistTreeBytes => new byte[] {{ { string.Join(", ", defDistTreeBytes) } }};"); + } + } +}