Skip to content

Commit d9e8d79

Browse files
committed
Reduced intermediate allocations: Webp
1 parent b099bda commit d9e8d79

17 files changed

+212
-221
lines changed

src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public void FillBitWindow()
192192
[MethodImpl(InliningOptions.ShortMethod)]
193193
private void ShiftBytes()
194194
{
195-
System.Span<byte> dataSpan = this.Data!.Memory.Span;
195+
Span<byte> dataSpan = this.Data!.Memory.Span;
196196
while (this.bitPos >= 8 && this.pos < this.len)
197197
{
198198
this.value >>= 8;

src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers.Binary;
5+
using System.Runtime.InteropServices;
56
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
67
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
78

@@ -23,7 +24,7 @@ internal abstract class BitWriterBase
2324
/// <summary>
2425
/// A scratch buffer to reduce allocations.
2526
/// </summary>
26-
private readonly byte[] scratchBuffer = new byte[4];
27+
private ScratchBuffer scratchBuffer; // mutable struct, don't make readonly
2728

2829
/// <summary>
2930
/// Initializes a new instance of the <see cref="BitWriterBase"/> class.
@@ -90,8 +91,8 @@ protected void ResizeBuffer(int maxBytes, int sizeRequired)
9091
protected void WriteRiffHeader(Stream stream, uint riffSize)
9192
{
9293
stream.Write(WebpConstants.RiffFourCc);
93-
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, riffSize);
94-
stream.Write(this.scratchBuffer.AsSpan(0, 4));
94+
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer.Span, riffSize);
95+
stream.Write(this.scratchBuffer.Span.Slice(0, 4));
9596
stream.Write(WebpConstants.WebpHeader);
9697
}
9798

@@ -128,7 +129,7 @@ protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpCh
128129
DebugGuard.NotNull(metadataBytes, nameof(metadataBytes));
129130

130131
uint size = (uint)metadataBytes.Length;
131-
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
132+
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
132133
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType);
133134
stream.Write(buf);
134135
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@@ -151,7 +152,7 @@ protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpCh
151152
protected void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDataIsCompressed)
152153
{
153154
uint size = (uint)dataBytes.Length + 1;
154-
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
155+
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
155156
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha);
156157
stream.Write(buf);
157158
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@@ -182,7 +183,7 @@ protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes)
182183
{
183184
uint size = (uint)iccProfileBytes.Length;
184185

185-
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
186+
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
186187
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Iccp);
187188
stream.Write(buf);
188189
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
@@ -245,7 +246,7 @@ protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfi
245246
flags |= 32;
246247
}
247248

248-
Span<byte> buf = this.scratchBuffer.AsSpan(0, 4);
249+
Span<byte> buf = this.scratchBuffer.Span.Slice(0, 4);
249250
stream.Write(WebpConstants.Vp8XMagicBytes);
250251
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
251252
stream.Write(buf);
@@ -256,4 +257,11 @@ protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfi
256257
BinaryPrimitives.WriteUInt32LittleEndian(buf, height - 1);
257258
stream.Write(buf[..3]);
258259
}
260+
261+
private unsafe struct ScratchBuffer
262+
{
263+
private fixed byte scratch[4];
264+
265+
public Span<byte> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], 4);
266+
}
259267
}

src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter;
1414
/// </summary>
1515
internal class Vp8LBitWriter : BitWriterBase
1616
{
17-
/// <summary>
18-
/// A scratch buffer to reduce allocations.
19-
/// </summary>
20-
private readonly byte[] scratchBuffer = new byte[8];
21-
2217
/// <summary>
2318
/// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed.
2419
/// </summary>
@@ -194,8 +189,9 @@ public void WriteEncodedImageToStream(Stream stream, ExifProfile? exifProfile, X
194189
stream.Write(WebpConstants.Vp8LMagicBytes);
195190

196191
// Write Vp8 Header.
197-
BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, size);
198-
stream.Write(this.scratchBuffer.AsSpan(0, 4));
192+
Span<byte> scratchBuffer = stackalloc byte[8];
193+
BinaryPrimitives.WriteUInt32LittleEndian(scratchBuffer, size);
194+
stream.Write(scratchBuffer.Slice(0, 4));
199195
stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte);
200196

201197
// Write the encoded bytes of the image to the stream.
@@ -228,8 +224,9 @@ private void PutBitsFlushBits()
228224
this.BitWriterResize(extraSize);
229225
}
230226

231-
BinaryPrimitives.WriteUInt64LittleEndian(this.scratchBuffer, this.bits);
232-
this.scratchBuffer.AsSpan(0, 4).CopyTo(this.Buffer.AsSpan(this.cur));
227+
Span<byte> scratchBuffer = stackalloc byte[8];
228+
BinaryPrimitives.WriteUInt64LittleEndian(scratchBuffer, this.bits);
229+
scratchBuffer.Slice(0, 4).CopyTo(this.Buffer.AsSpan(this.cur));
233230

234231
this.cur += WriterBytes;
235232
this.bits >>= WriterBits;

src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace SixLabors.ImageSharp.Formats.Webp.Lossless;
88

9-
internal class HistogramEncoder
9+
internal static class HistogramEncoder
1010
{
1111
/// <summary>
1212
/// Number of partitions for the three dominant (literal, red and blue) symbol costs.
@@ -27,7 +27,7 @@ internal class HistogramEncoder
2727

2828
private const ushort InvalidHistogramSymbol = ushort.MaxValue;
2929

30-
public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, uint quality, int histoBits, int cacheBits, List<Vp8LHistogram> imageHisto, Vp8LHistogram tmpHisto, ushort[] histogramSymbols)
30+
public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, uint quality, int histoBits, int cacheBits, List<Vp8LHistogram> imageHisto, Vp8LHistogram tmpHisto, Span<ushort> histogramSymbols)
3131
{
3232
int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1;
3333
int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1;
@@ -148,7 +148,7 @@ private static void HistogramAnalyzeEntropyBin(List<Vp8LHistogram> histograms, u
148148
}
149149
}
150150

151-
private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, ushort[] histogramSymbols)
151+
private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, List<Vp8LHistogram> histograms, Span<ushort> histogramSymbols)
152152
{
153153
var stats = new Vp8LStreaks();
154154
var bitsEntropy = new Vp8LBitEntropy();
@@ -171,20 +171,28 @@ private static int HistogramCopyAndAnalyze(List<Vp8LHistogram> origHistograms, L
171171
}
172172
}
173173

174-
int numUsed = histogramSymbols.Count(h => h != InvalidHistogramSymbol);
174+
int numUsed = 0;
175+
foreach (ushort h in histogramSymbols)
176+
{
177+
if (h != InvalidHistogramSymbol)
178+
{
179+
numUsed++;
180+
}
181+
}
182+
175183
return numUsed;
176184
}
177185

178186
private static void HistogramCombineEntropyBin(
179187
List<Vp8LHistogram> histograms,
180-
ushort[] clusters,
188+
Span<ushort> clusters,
181189
ushort[] clusterMappings,
182190
Vp8LHistogram curCombo,
183191
ushort[] binMap,
184192
int numBins,
185193
double combineCostFactor)
186194
{
187-
var binInfo = new HistogramBinInfo[BinSize];
195+
Span<HistogramBinInfo> binInfo = stackalloc HistogramBinInfo[BinSize];
188196
for (int idx = 0; idx < numBins; idx++)
189197
{
190198
binInfo[idx].First = -1;
@@ -258,7 +266,7 @@ private static void HistogramCombineEntropyBin(
258266
/// Given a Histogram set, the mapping of clusters 'clusterMapping' and the
259267
/// current assignment of the cells in 'symbols', merge the clusters and assign the smallest possible clusters values.
260268
/// </summary>
261-
private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, ushort[] symbols)
269+
private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, Span<ushort> symbols)
262270
{
263271
bool doContinue = true;
264272

@@ -331,7 +339,7 @@ private static bool HistogramCombineStochastic(List<Vp8LHistogram> histograms, i
331339
int maxSize = 9;
332340

333341
// Fill the initial mapping.
334-
int[] mappings = new int[histograms.Count];
342+
Span<int> mappings = histograms.Count <= 64 ? stackalloc int[histograms.Count] : new int[histograms.Count];
335343
for (int j = 0, iter = 0; iter < histograms.Count; iter++)
336344
{
337345
if (histograms[iter] == null)
@@ -388,9 +396,9 @@ private static bool HistogramCombineStochastic(List<Vp8LHistogram> histograms, i
388396
int bestIdx1 = histoPriorityList[0].Idx1;
389397
int bestIdx2 = histoPriorityList[0].Idx2;
390398

391-
int mappingIndex = Array.IndexOf(mappings, bestIdx2);
392-
Span<int> src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1);
393-
Span<int> dst = mappings.AsSpan(mappingIndex);
399+
int mappingIndex = mappings.IndexOf(bestIdx2);
400+
Span<int> src = mappings.Slice(mappingIndex + 1, numUsed - mappingIndex - 1);
401+
Span<int> dst = mappings.Slice(mappingIndex);
394402
src.CopyTo(dst);
395403

396404
// Merge the histograms and remove bestIdx2 from the list.
@@ -528,7 +536,7 @@ private static void HistogramCombineGreedy(List<Vp8LHistogram> histograms)
528536
}
529537
}
530538

531-
private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram> output, ushort[] symbols)
539+
private static void HistogramRemap(List<Vp8LHistogram> input, List<Vp8LHistogram> output, Span<ushort> symbols)
532540
{
533541
int inSize = input.Count;
534542
int outSize = output.Count;

src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ internal static class HuffmanUtils
2525
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf
2626
};
2727

28-
public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, HuffmanTree[] huffTree, HuffmanTreeCode huffCode)
28+
public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, Span<HuffmanTree> huffTree, HuffmanTreeCode huffCode)
2929
{
3030
int numSymbols = huffCode.NumSymbols;
3131
bufRle.AsSpan().Clear();
@@ -159,7 +159,7 @@ public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, uint[] c
159159
/// <param name="histogramSize">The size of the histogram.</param>
160160
/// <param name="treeDepthLimit">The tree depth limit.</param>
161161
/// <param name="bitDepths">How many bits are used for the symbol.</param>
162-
public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths)
162+
public static void GenerateOptimalTree(Span<HuffmanTree> tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths)
163163
{
164164
uint countMin;
165165
int treeSizeOrig = 0;
@@ -177,7 +177,7 @@ public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int
177177
return;
178178
}
179179

180-
Span<HuffmanTree> treePool = tree.AsSpan(treeSizeOrig);
180+
Span<HuffmanTree> treePool = tree.Slice(treeSizeOrig);
181181

182182
// For block sizes with less than 64k symbols we never need to do a
183183
// second iteration of this loop.
@@ -202,14 +202,8 @@ public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int
202202
}
203203

204204
// Build the Huffman tree.
205-
#if NET5_0_OR_GREATER
206-
Span<HuffmanTree> treeSlice = tree.AsSpan(0, treeSize);
205+
Span<HuffmanTree> treeSlice = tree.Slice(0, treeSize);
207206
treeSlice.Sort(HuffmanTree.Compare);
208-
#else
209-
HuffmanTree[] treeCopy = tree.AsSpan(0, treeSize).ToArray();
210-
Array.Sort(treeCopy, HuffmanTree.Compare);
211-
treeCopy.AsSpan().CopyTo(tree);
212-
#endif
213207

214208
if (treeSize > 1)
215209
{
@@ -312,12 +306,12 @@ public static int BuildHuffmanTable(Span<HuffmanCode> table, int rootBits, int[]
312306
DebugGuard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize));
313307

314308
// sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length.
315-
int[] sorted = new int[codeLengthsSize];
309+
Span<int> sorted = codeLengthsSize <= 64 ? stackalloc int[codeLengthsSize] : new int[codeLengthsSize];
316310
int totalSize = 1 << rootBits; // total size root table + 2nd level table.
317311
int len; // current code length.
318312
int symbol; // symbol index in original or sorted table.
319-
int[] counts = new int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length.
320-
int[] offsets = new int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length.
313+
Span<int> counts = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length.
314+
Span<int> offsets = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length.
321315

322316
// Build histogram of code lengths.
323317
for (symbol = 0; symbol < codeLengthsSize; ++symbol)
@@ -544,8 +538,8 @@ private static int CodeRepeatedValues(int repetitions, Span<HuffmanTreeToken> to
544538
private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree)
545539
{
546540
// 0 bit-depth means that the symbol does not exist.
547-
uint[] nextCode = new uint[WebpConstants.MaxAllowedCodeLength + 1];
548-
int[] depthCount = new int[WebpConstants.MaxAllowedCodeLength + 1];
541+
Span<uint> nextCode = stackalloc uint[WebpConstants.MaxAllowedCodeLength + 1];
542+
Span<int> depthCount = stackalloc int[WebpConstants.MaxAllowedCodeLength + 1];
549543

550544
int len = tree.NumSymbols;
551545
for (int i = 0; i < len; i++)
@@ -603,7 +597,7 @@ private static uint ReverseBits(int numBits, uint bits)
603597
/// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols,
604598
/// len is the code length of the next processed symbol.
605599
/// </summary>
606-
private static int NextTableBitSize(int[] count, int len, int rootBits)
600+
private static int NextTableBitSize(ReadOnlySpan<int> count, int len, int rootBits)
607601
{
608602
int left = 1 << (len - rootBits);
609603
while (len < WebpConstants.MaxAllowedCodeLength)

src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,13 @@ public static void ResidualImage(
5757
Span<short> scratch = stackalloc short[8];
5858

5959
// TODO: Can we optimize this?
60-
int[][] histo = new int[4][];
61-
for (int i = 0; i < 4; i++)
60+
int[][] histo =
6261
{
63-
histo[i] = new int[256];
64-
}
62+
new int[256],
63+
new int[256],
64+
new int[256],
65+
new int[256]
66+
};
6567

6668
if (lowEffort)
6769
{
@@ -233,7 +235,7 @@ private static int GetBestPredictorForTile(
233235
Span<byte> maxDiffs = MemoryMarshal.Cast<uint, byte>(currentRow[(width + 1)..]);
234236
float bestDiff = MaxDiffCost;
235237
int bestMode = 0;
236-
uint[] residuals = new uint[1 << WebpConstants.MaxTransformBits];
238+
Span<uint> residuals = stackalloc uint[1 << WebpConstants.MaxTransformBits]; // 256 bytes
237239
for (int i = 0; i < 4; i++)
238240
{
239241
histoArgb[i].AsSpan().Clear();
@@ -299,9 +301,7 @@ private static int GetBestPredictorForTile(
299301

300302
if (curDiff < bestDiff)
301303
{
302-
int[][] tmp = histoArgb;
303-
histoArgb = bestHisto;
304-
bestHisto = tmp;
304+
(bestHisto, histoArgb) = (histoArgb, bestHisto);
305305
bestDiff = curDiff;
306306
bestMode = mode;
307307
}

0 commit comments

Comments
 (0)