From 8a631cec77fe6a03f3dbc15c21855205cb350b1d Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Mon, 6 Feb 2023 08:34:10 -0500 Subject: [PATCH] Rewrite decompressor RefillTagFromScatch method to use refs (#49) Motivation ---------- This is a step towards code that doesn't require pinning which can help with GC when compression/decompression is run a lot. GC will be able to move memory even when in the middle of a compression or decompression run and update the ref pointers. Modifications ------------- Rewrite RefillTagFromScratch to use refs, returning a ValueTuple so that the pointer may be incremented the correct amount by the caller. Results ------- .NET 6 shows a per improvement, .NET 7 is neutral. .NET 4.8 shows a slight regression, I'm assuming due to poor enregistration. BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22000.1455/21H2) Intel Core i7-10850H CPU 2.70GHz, 1 CPU, 12 logical and 6 physical cores .NET SDK=7.0.102 [Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2 MediumRun-.NET 6.0 : .NET 6.0.13 (6.0.1322.58009), X64 RyuJIT AVX2 MediumRun-.NET 7.0 : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2 MediumRun-.NET Framework 4.8 : .NET Framework 4.8 (4.8.4515.0), X64 RyuJIT VectorSize=256 IterationCount=15 LaunchCount=2 WarmupCount=10 | Method | Job | Runtime | Mean | Error | StdDev | Median | Ratio | RatioSD | |-------- |----------------------------- |------------------- |----------:|---------:|---------:|----------:|------:|--------:| | Pointer | MediumRun-.NET 6.0 | .NET 6.0 | 109.06 us | 1.150 us | 1.686 us | 108.60 us | 1.00 | 0.00 | | Ref | MediumRun-.NET 6.0 | .NET 6.0 | 106.20 us | 0.863 us | 1.265 us | 106.47 us | 0.97 | 0.02 | | | | | | | | | | | | Pointer | MediumRun-.NET 7.0 | .NET 7.0 | 93.34 us | 0.877 us | 1.257 us | 93.13 us | 1.00 | 0.00 | | Ref | MediumRun-.NET 7.0 | .NET 7.0 | 94.00 us | 1.161 us | 1.738 us | 95.01 us | 1.01 | 0.03 | | | | | | | | | | | | Pointer | MediumRun-.NET Framework 4.8 | .NET Framework 4.8 | 106.36 us | 0.357 us | 0.501 us | 106.42 us | 1.00 | 0.00 | | Ref | MediumRun-.NET Framework 4.8 | .NET Framework 4.8 | 111.34 us | 1.041 us | 1.558 us | 110.50 us | 1.05 | 0.02 | --- Snappier/Internal/SnappyDecompressor.cs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Snappier/Internal/SnappyDecompressor.cs b/Snappier/Internal/SnappyDecompressor.cs index 15ae694..ee00b24 100644 --- a/Snappier/Internal/SnappyDecompressor.cs +++ b/Snappier/Internal/SnappyDecompressor.cs @@ -210,7 +210,10 @@ internal unsafe void DecompressAllTags(ReadOnlySpan inputSpan) // and any literal data from the _input buffer // scratch will be the scratch buffer with only the tag if true is returned - if (!RefillTagFromScratch(ref input, inputEnd, scratch)) + (bool sufficientData, uint inputUsed) = RefillTagFromScratch(ref Unsafe.AsRef(input), + ref Unsafe.AsRef(inputEnd), ref Unsafe.AsRef(scratch)); + input += inputUsed; + if (!sufficientData) { return; } @@ -416,33 +419,31 @@ internal unsafe void DecompressAllTags(ReadOnlySpan inputSpan) } } - private unsafe bool RefillTagFromScratch(ref byte* input, byte* inputEnd, byte* scratch) + private (bool sufficientData, uint inputUsed) RefillTagFromScratch(ref byte input, ref byte inputEnd, ref byte scratch) { Debug.Assert(_scratchLength > 0); - if (input >= inputEnd) + if (!Unsafe.IsAddressLessThan(ref input, ref inputEnd)) { - return false; + return (false, 0); } // Read the tag character - byte c = *scratch; - uint entry = Constants.CharTable[c]; + uint entry = Constants.CharTable[scratch]; uint needed = (entry >> 11) + 1; // +1 byte for 'c' - uint toCopy = Math.Min(unchecked((uint)(inputEnd - input)), needed - _scratchLength); - Unsafe.CopyBlockUnaligned(scratch + _scratchLength, input, toCopy); + uint toCopy = Math.Min((uint)Unsafe.ByteOffset(ref input, ref inputEnd), needed - _scratchLength); + Unsafe.CopyBlockUnaligned(ref Unsafe.Add(ref scratch, _scratchLength), ref input, toCopy); _scratchLength += toCopy; - input += toCopy; if (_scratchLength < needed) { // Still insufficient - return false; + return (false, toCopy); } - return true; + return (true, toCopy); } private unsafe bool RefillTag(ref byte* input, ref byte* inputEnd, byte* scratch)