From 1d3ae0ed9dc3ad0a20efb6e2650e2d08b24624bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 25 Mar 2023 22:36:30 +0100 Subject: [PATCH] Reduced intermediate allocations: Gif --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 29 +++++++++++------- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 32 ++++++++++---------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 6ff2723ddd..efde4e9aa8 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -22,7 +22,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals /// /// The temp buffer used to reduce allocations. /// - private readonly byte[] buffer = new byte[16]; + private ScratchBuffer buffer; // mutable struct, don't make readonly /// /// The global color table. @@ -249,13 +249,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals /// The containing image data. private void ReadGraphicalControlExtension(BufferedReadStream stream) { - int bytesRead = stream.Read(this.buffer, 0, 6); + int bytesRead = stream.Read(this.buffer.Span, 0, 6); if (bytesRead != 6) { GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension"); } - this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer); + this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer.Span); } /// @@ -264,13 +264,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals /// The containing image data. private void ReadImageDescriptor(BufferedReadStream stream) { - int bytesRead = stream.Read(this.buffer, 0, 9); + int bytesRead = stream.Read(this.buffer.Span, 0, 9); if (bytesRead != 9) { GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor"); } - this.imageDescriptor = GifImageDescriptor.Parse(this.buffer); + this.imageDescriptor = GifImageDescriptor.Parse(this.buffer.Span); if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0) { GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0"); @@ -283,13 +283,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals /// The containing image data. private void ReadLogicalScreenDescriptor(BufferedReadStream stream) { - int bytesRead = stream.Read(this.buffer, 0, 7); + int bytesRead = stream.Read(this.buffer.Span, 0, 7); if (bytesRead != 7) { GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor"); } - this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer); + this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer.Span); } /// @@ -306,8 +306,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals long position = stream.Position; if (appLength == GifConstants.ApplicationBlockSize) { - stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize); - bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes); + stream.Read(this.buffer.Span, 0, GifConstants.ApplicationBlockSize); + bool isXmp = this.buffer.Span.StartsWith(GifConstants.XmpApplicationIdentificationBytes); if (isXmp && !this.skipMetadata) { GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator); @@ -331,8 +331,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) { - stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize); - this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount; + stream.Read(this.buffer.Span, 0, GifConstants.NetscapeLoopingSubBlockSize); + this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.Span.Slice(1)).RepeatCount; stream.Skip(1); // Skip the terminator. return; } @@ -762,4 +762,11 @@ internal sealed class GifDecoderCore : IImageDecoderInternals } } } + + private unsafe struct ScratchBuffer + { + private fixed byte scratch[16]; + + public Span Span => MemoryMarshal.CreateSpan(ref this.scratch[0], 16); + } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index f736da78dd..c01cc78ef0 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -28,11 +28,6 @@ internal sealed class GifEncoderCore : IImageEncoderInternals /// private readonly Configuration configuration; - /// - /// A reusable buffer used to reduce allocations. - /// - private readonly byte[] buffer = new byte[20]; - /// /// Whether to skip metadata during encode. /// @@ -324,9 +319,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals backgroundColorIndex: unchecked((byte)transparencyIndex), ratio); - descriptor.WriteTo(this.buffer); + Span buffer = stackalloc byte[20]; + descriptor.WriteTo(buffer); - stream.Write(this.buffer, 0, GifLogicalScreenDescriptor.Size); + stream.Write(buffer, 0, GifLogicalScreenDescriptor.Size); } /// @@ -365,12 +361,14 @@ internal sealed class GifEncoderCore : IImageEncoderInternals return; } + Span buffer = stackalloc byte[2]; + for (int i = 0; i < metadata.Comments.Count; i++) { string comment = metadata.Comments[i]; - this.buffer[0] = GifConstants.ExtensionIntroducer; - this.buffer[1] = GifConstants.CommentLabel; - stream.Write(this.buffer, 0, 2); + buffer[1] = GifConstants.CommentLabel; + buffer[0] = GifConstants.ExtensionIntroducer; + stream.Write(buffer); // Comment will be stored in chunks of 255 bytes, if it exceeds this size. ReadOnlySpan commentSpan = comment.AsSpan(); @@ -437,22 +435,23 @@ internal sealed class GifEncoderCore : IImageEncoderInternals private void WriteExtension(TGifExtension extension, Stream stream) where TGifExtension : struct, IGifExtension { - IMemoryOwner? owner = null; - Span extensionBuffer; int extensionSize = extension.ContentLength; if (extensionSize == 0) { return; } - else if (extensionSize > this.buffer.Length - 3) + + IMemoryOwner? owner = null; + Span extensionBuffer = stackalloc byte[0]; // workaround compiler limitation + if (extensionSize > 128) { owner = this.memoryAllocator.Allocate(extensionSize + 3); extensionBuffer = owner.GetSpan(); } else { - extensionBuffer = this.buffer; + extensionBuffer = stackalloc byte[extensionSize + 3]; } extensionBuffer[0] = GifConstants.ExtensionIntroducer; @@ -489,9 +488,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals height: (ushort)image.Height, packed: packedValue); - descriptor.WriteTo(this.buffer); + Span buffer = stackalloc byte[20]; + descriptor.WriteTo(buffer); - stream.Write(this.buffer, 0, GifImageDescriptor.Size); + stream.Write(buffer, 0, GifImageDescriptor.Size); } ///