Skip to content

Commit 1d3ae0e

Browse files
committed
Reduced intermediate allocations: Gif
1 parent 858a848 commit 1d3ae0e

File tree

2 files changed

+34
-27
lines changed

2 files changed

+34
-27
lines changed

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
2222
/// <summary>
2323
/// The temp buffer used to reduce allocations.
2424
/// </summary>
25-
private readonly byte[] buffer = new byte[16];
25+
private ScratchBuffer buffer; // mutable struct, don't make readonly
2626

2727
/// <summary>
2828
/// The global color table.
@@ -249,13 +249,13 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
249249
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
250250
private void ReadGraphicalControlExtension(BufferedReadStream stream)
251251
{
252-
int bytesRead = stream.Read(this.buffer, 0, 6);
252+
int bytesRead = stream.Read(this.buffer.Span, 0, 6);
253253
if (bytesRead != 6)
254254
{
255255
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension");
256256
}
257257

258-
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer);
258+
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer.Span);
259259
}
260260

261261
/// <summary>
@@ -264,13 +264,13 @@ private void ReadGraphicalControlExtension(BufferedReadStream stream)
264264
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
265265
private void ReadImageDescriptor(BufferedReadStream stream)
266266
{
267-
int bytesRead = stream.Read(this.buffer, 0, 9);
267+
int bytesRead = stream.Read(this.buffer.Span, 0, 9);
268268
if (bytesRead != 9)
269269
{
270270
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor");
271271
}
272272

273-
this.imageDescriptor = GifImageDescriptor.Parse(this.buffer);
273+
this.imageDescriptor = GifImageDescriptor.Parse(this.buffer.Span);
274274
if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0)
275275
{
276276
GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0");
@@ -283,13 +283,13 @@ private void ReadImageDescriptor(BufferedReadStream stream)
283283
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
284284
private void ReadLogicalScreenDescriptor(BufferedReadStream stream)
285285
{
286-
int bytesRead = stream.Read(this.buffer, 0, 7);
286+
int bytesRead = stream.Read(this.buffer.Span, 0, 7);
287287
if (bytesRead != 7)
288288
{
289289
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor");
290290
}
291291

292-
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
292+
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer.Span);
293293
}
294294

295295
/// <summary>
@@ -306,8 +306,8 @@ private void ReadApplicationExtension(BufferedReadStream stream)
306306
long position = stream.Position;
307307
if (appLength == GifConstants.ApplicationBlockSize)
308308
{
309-
stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize);
310-
bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes);
309+
stream.Read(this.buffer.Span, 0, GifConstants.ApplicationBlockSize);
310+
bool isXmp = this.buffer.Span.StartsWith(GifConstants.XmpApplicationIdentificationBytes);
311311
if (isXmp && !this.skipMetadata)
312312
{
313313
GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator);
@@ -331,8 +331,8 @@ private void ReadApplicationExtension(BufferedReadStream stream)
331331
// http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
332332
if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize)
333333
{
334-
stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize);
335-
this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount;
334+
stream.Read(this.buffer.Span, 0, GifConstants.NetscapeLoopingSubBlockSize);
335+
this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.Span.Slice(1)).RepeatCount;
336336
stream.Skip(1); // Skip the terminator.
337337
return;
338338
}
@@ -762,4 +762,11 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream s
762762
}
763763
}
764764
}
765+
766+
private unsafe struct ScratchBuffer
767+
{
768+
private fixed byte scratch[16];
769+
770+
public Span<byte> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], 16);
771+
}
765772
}

src/ImageSharp/Formats/Gif/GifEncoderCore.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
2828
/// </summary>
2929
private readonly Configuration configuration;
3030

31-
/// <summary>
32-
/// A reusable buffer used to reduce allocations.
33-
/// </summary>
34-
private readonly byte[] buffer = new byte[20];
35-
3631
/// <summary>
3732
/// Whether to skip metadata during encode.
3833
/// </summary>
@@ -324,9 +319,10 @@ private void WriteLogicalScreenDescriptor(
324319
backgroundColorIndex: unchecked((byte)transparencyIndex),
325320
ratio);
326321

327-
descriptor.WriteTo(this.buffer);
322+
Span<byte> buffer = stackalloc byte[20];
323+
descriptor.WriteTo(buffer);
328324

329-
stream.Write(this.buffer, 0, GifLogicalScreenDescriptor.Size);
325+
stream.Write(buffer, 0, GifLogicalScreenDescriptor.Size);
330326
}
331327

332328
/// <summary>
@@ -365,12 +361,14 @@ private void WriteComments(GifMetadata metadata, Stream stream)
365361
return;
366362
}
367363

364+
Span<byte> buffer = stackalloc byte[2];
365+
368366
for (int i = 0; i < metadata.Comments.Count; i++)
369367
{
370368
string comment = metadata.Comments[i];
371-
this.buffer[0] = GifConstants.ExtensionIntroducer;
372-
this.buffer[1] = GifConstants.CommentLabel;
373-
stream.Write(this.buffer, 0, 2);
369+
buffer[1] = GifConstants.CommentLabel;
370+
buffer[0] = GifConstants.ExtensionIntroducer;
371+
stream.Write(buffer);
374372

375373
// Comment will be stored in chunks of 255 bytes, if it exceeds this size.
376374
ReadOnlySpan<char> commentSpan = comment.AsSpan();
@@ -437,22 +435,23 @@ private void WriteGraphicalControlExtension(GifFrameMetadata metadata, int trans
437435
private void WriteExtension<TGifExtension>(TGifExtension extension, Stream stream)
438436
where TGifExtension : struct, IGifExtension
439437
{
440-
IMemoryOwner<byte>? owner = null;
441-
Span<byte> extensionBuffer;
442438
int extensionSize = extension.ContentLength;
443439

444440
if (extensionSize == 0)
445441
{
446442
return;
447443
}
448-
else if (extensionSize > this.buffer.Length - 3)
444+
445+
IMemoryOwner<byte>? owner = null;
446+
Span<byte> extensionBuffer = stackalloc byte[0]; // workaround compiler limitation
447+
if (extensionSize > 128)
449448
{
450449
owner = this.memoryAllocator.Allocate<byte>(extensionSize + 3);
451450
extensionBuffer = owner.GetSpan();
452451
}
453452
else
454453
{
455-
extensionBuffer = this.buffer;
454+
extensionBuffer = stackalloc byte[extensionSize + 3];
456455
}
457456

458457
extensionBuffer[0] = GifConstants.ExtensionIntroducer;
@@ -489,9 +488,10 @@ private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, bool hasColo
489488
height: (ushort)image.Height,
490489
packed: packedValue);
491490

492-
descriptor.WriteTo(this.buffer);
491+
Span<byte> buffer = stackalloc byte[20];
492+
descriptor.WriteTo(buffer);
493493

494-
stream.Write(this.buffer, 0, GifImageDescriptor.Size);
494+
stream.Write(buffer, 0, GifImageDescriptor.Size);
495495
}
496496

497497
/// <summary>

0 commit comments

Comments
 (0)