From 003a82a09eb3badf7b5dc76972d70cb7737b0168 Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Sat, 6 Jan 2024 10:02:41 -0500 Subject: [PATCH] Handle null chunk type using enum value Motivation ---------- Trying to narrow down perf loss in .NET 8 compared to .NET 6 specific to stream decompression. Modifications ------------- Rather than using a `Nullable` to track when the chunk type is unknown, add an enum value that represents null. 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-AODULB : .NET Framework 4.8.1 (4.8.9181.0), X64 RyuJIT VectorSize=256 Job-AZPVRO : .NET Framework 4.8.1 (4.8.9181.0), X64 RyuJIT VectorSize=256 Job-FTJSLO : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2 Job-XMQQAK : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2 Job-IPTUCK : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 Job-YAGJLP : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 Job-DJZUQL : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 Job-FFPERJ : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 | Method | Runtime | BuildConfiguration | PGO | ReadSize | Mean | Error | StdDev | Ratio | Rank | |----------- |------------------- |------------------- |---- |--------- |---------:|--------:|--------:|------:|-----:| | Decompress | .NET Framework 4.8 | Previous | N | 16384 | 340.4 us | 2.56 us | 2.39 us | 1.00 | 2 | | Decompress | .NET Framework 4.8 | Default | N | 16384 | 332.2 us | 2.64 us | 2.34 us | 0.98 | 1 | | | | | | | | | | | | | Decompress | .NET 6.0 | Previous | N | 16384 | 167.5 us | 1.11 us | 1.04 us | 1.00 | 1 | | Decompress | .NET 6.0 | Default | N | 16384 | 167.5 us | 2.44 us | 2.16 us | 1.00 | 1 | | | | | | | | | | | | | Decompress | .NET 8.0 | Previous | N | 16384 | 200.7 us | 1.19 us | 1.06 us | 1.00 | 2 | | Decompress | .NET 8.0 | Default | N | 16384 | 191.7 us | 1.47 us | 1.30 us | 0.96 | 1 | | | | | | | | | | | | | Decompress | .NET 8.0 | Previous | Y | 16384 | 199.1 us | 0.94 us | 0.88 us | 1.00 | 2 | | Decompress | .NET 8.0 | Default | Y | 16384 | 189.9 us | 0.91 us | 0.85 us | 0.95 | 1 | Note: Benchmarks include other improvements since 1.1.3 --- Snappier/Internal/Constants.cs | 6 +++++- Snappier/Internal/SnappyStreamDecompressor.cs | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Snappier/Internal/Constants.cs b/Snappier/Internal/Constants.cs index 3e62229..8c568f0 100644 --- a/Snappier/Internal/Constants.cs +++ b/Snappier/Internal/Constants.cs @@ -10,7 +10,11 @@ public enum ChunkType : byte UncompressedData = 0x01, SkippableChunk = 0x80, // If this bit is set, we can safely skip the chunk if unknown Padding = 0xfe, - StreamIdentifier = 0xff + StreamIdentifier = 0xff, + + // This is not part of the spec, but having this extra value representing null avoids + // the cost of wrapping in a Nullable + Null = 0xfd, } public const byte Literal = 0b00; diff --git a/Snappier/Internal/SnappyStreamDecompressor.cs b/Snappier/Internal/SnappyStreamDecompressor.cs index c938bac..6558f20 100644 --- a/Snappier/Internal/SnappyStreamDecompressor.cs +++ b/Snappier/Internal/SnappyStreamDecompressor.cs @@ -19,7 +19,7 @@ internal sealed class SnappyStreamDecompressor : IDisposable private readonly byte[] _scratch = new byte[ScratchBufferSize]; private int _scratchLength; - private Constants.ChunkType? _chunkType; + private Constants.ChunkType _chunkType = Constants.ChunkType.Null; private int _chunkSize; private int _chunkBytesProcessed; private uint _expectedChunkCrc; @@ -40,7 +40,7 @@ public int Decompress(Span buffer) // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault switch (_chunkType) { - case null: + case Constants.ChunkType.Null: // Not in a chunk, read the chunk type and size uint rawChunkHeader = ReadChunkHeader(ref input); @@ -106,7 +106,7 @@ public int Decompress(Span buffer) if (_decompressor.EndOfFile) { // Completed reading the chunk - _chunkType = null; + _chunkType = Constants.ChunkType.Null; uint crc = Crc32CAlgorithm.ApplyMask(_chunkCrc); if (_expectedChunkCrc != crc) @@ -149,7 +149,7 @@ public int Decompress(Span buffer) if (_chunkBytesProcessed >= _chunkSize) { // Completed reading the chunk - _chunkType = null; + _chunkType = Constants.ChunkType.Null; uint crc = Crc32CAlgorithm.ApplyMask(_chunkCrc); if (_expectedChunkCrc != crc) @@ -176,7 +176,7 @@ public int Decompress(Span buffer) if (_chunkBytesProcessed >= _chunkSize) { // Completed reading the chunk - _chunkType = null; + _chunkType = Constants.ChunkType.Null; } break;