From be80b886d59b0f9a9265fb605ca7cf9ca5b600ca Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Mon, 8 Jan 2024 08:26:45 -0500 Subject: [PATCH] Use an inline array for decompression scratch Motivation ---------- This array is small, fixed size, and always created. We can reduce heap allocations if it's inline within the class rather than a separate object on the heap. Modifications ------------- For .NET 8 only use a fixed array to store the block decompression scratch buffer. Results ------- BenchmarkDotNet v0.13.10, Windows 11 (10.0.22621.2861/22H2/2022Update/SunValley2) 12th Gen Intel Core i7-1270P, 1 CPU, 16 logical and 12 physical cores .NET SDK 8.0.100 [Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 Job-SOJPFQ : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 Job-OKVMCF : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 Job-BRDMES : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2 Job-AJAIVB : .NET Framework 4.8.1 (4.8.9181.0), X64 RyuJIT VectorSize=256 | Method | Runtime | PGO | Mean | Error | StdDev | Ratio | RatioSD | Rank | Allocated | Alloc Ratio | |------------ |------------------- |---- |---------:|---------:|---------:|------:|--------:|-----:|----------:|------------:| | Array | .NET 8.0 | N | 10.77 us | 0.142 us | 0.132 us | 1.00 | 0.00 | 2 | 112 B | 1.00 | | InlineArray | .NET 8.0 | N | 10.53 us | 0.168 us | 0.157 us | 0.98 | 0.02 | 1 | 80 B | 0.71 | | | | | | | | | | | | | | Array | .NET 8.0 | Y | 10.14 us | 0.114 us | 0.106 us | 1.00 | 0.00 | 1 | 112 B | 1.00 | | InlineArray | .NET 8.0 | Y | 10.11 us | 0.080 us | 0.071 us | 1.00 | 0.02 | 1 | 80 B | 0.71 | --- Snappier/Internal/SnappyDecompressor.cs | 26 +++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Snappier/Internal/SnappyDecompressor.cs b/Snappier/Internal/SnappyDecompressor.cs index c97d3ce..a3d897c 100644 --- a/Snappier/Internal/SnappyDecompressor.cs +++ b/Snappier/Internal/SnappyDecompressor.cs @@ -9,7 +9,24 @@ namespace Snappier.Internal { internal sealed class SnappyDecompressor : IDisposable { - private byte[] _scratch = new byte[Constants.MaximumTagLength]; +#if NET8_0_OR_GREATER +#pragma warning disable IDE0051 +#pragma warning disable IDE0044 +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + [InlineArray(Constants.MaximumTagLength)] + private struct ScratchBuffer + { + private byte _element0; + } + + private ScratchBuffer _scratch; +#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value +#pragma warning restore IDE0044 +#pragma warning restore IDE0051 +#else + private readonly byte[] _scratch = new byte[Constants.MaximumTagLength]; +#endif + private uint _scratchLength = 0; private int _remainingLiteral; @@ -671,7 +688,12 @@ internal void WriteToBufferForTest(ReadOnlySpan toWrite) internal void LoadScratchForTest(byte[] newScratch, uint newScratchLength) { ThrowHelper.ThrowIfNull(newScratch); - _scratch = newScratch; + if (newScratchLength > ((ReadOnlySpan)_scratch).Length) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(newScratchLength), "Scratch length exceeds limit"); + } + + newScratch.AsSpan(0, (int) newScratchLength).CopyTo(_scratch); _scratchLength = newScratchLength; }