Skip to content

Commit 1e58ba9

Browse files
Merge pull request #2940 from SladeThe/main
Reduce the number of memory allocations in lossless WebP encoder
2 parents 0c13a36 + a645b54 commit 1e58ba9

File tree

8 files changed

+89
-112
lines changed

8 files changed

+89
-112
lines changed

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

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,8 @@ private static int CalculateBestCacheSize(
149149
}
150150

151151
// Find the cacheBits giving the lowest entropy.
152-
for (int idx = 0; idx < refs.Refs.Count; idx++)
152+
foreach (PixOrCopy v in refs)
153153
{
154-
PixOrCopy v = refs.Refs[idx];
155154
if (v.IsLiteral())
156155
{
157156
uint pix = bgra[pos++];
@@ -387,7 +386,7 @@ private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan<uin
387386
colorCache = new ColorCache(cacheBits);
388387
}
389388

390-
backwardRefs.Refs.Clear();
389+
backwardRefs.Clear();
391390
for (int ix = 0; ix < chosenPathSize; ix++)
392391
{
393392
int len = chosenPath[ix];
@@ -479,7 +478,7 @@ private static void BackwardReferencesLz77(int xSize, int ySize, ReadOnlySpan<ui
479478
colorCache = new ColorCache(cacheBits);
480479
}
481480

482-
refs.Refs.Clear();
481+
refs.Clear();
483482
for (int i = 0; i < pixCount;)
484483
{
485484
// Alternative #1: Code the pixels starting at 'i' using backward reference.
@@ -734,7 +733,7 @@ private static void BackwardReferencesRle(int xSize, int ySize, ReadOnlySpan<uin
734733
colorCache = new ColorCache(cacheBits);
735734
}
736735

737-
refs.Refs.Clear();
736+
refs.Clear();
738737

739738
// Add first pixel as literal.
740739
AddSingleLiteral(bgra[0], useColorCache, colorCache, refs);
@@ -779,20 +778,17 @@ private static void BackwardReferencesRle(int xSize, int ySize, ReadOnlySpan<uin
779778
private static void BackwardRefsWithLocalCache(ReadOnlySpan<uint> bgra, int cacheBits, Vp8LBackwardRefs refs)
780779
{
781780
int pixelIndex = 0;
782-
ColorCache colorCache = new ColorCache(cacheBits);
783-
for (int idx = 0; idx < refs.Refs.Count; idx++)
781+
ColorCache colorCache = new(cacheBits);
782+
foreach (ref PixOrCopy v in refs)
784783
{
785-
PixOrCopy v = refs.Refs[idx];
786784
if (v.IsLiteral())
787785
{
788786
uint bgraLiteral = v.BgraOrDistance;
789787
int ix = colorCache.Contains(bgraLiteral);
790788
if (ix >= 0)
791789
{
792790
// Color cache contains bgraLiteral
793-
v.Mode = PixOrCopyMode.CacheIdx;
794-
v.BgraOrDistance = (uint)ix;
795-
v.Len = 1;
791+
v = PixOrCopy.CreateCacheIdx(ix);
796792
}
797793
else
798794
{
@@ -814,14 +810,13 @@ private static void BackwardRefsWithLocalCache(ReadOnlySpan<uint> bgra, int cach
814810

815811
private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs refs)
816812
{
817-
using List<PixOrCopy>.Enumerator c = refs.Refs.GetEnumerator();
818-
while (c.MoveNext())
813+
foreach (ref PixOrCopy v in refs)
819814
{
820-
if (c.Current.IsCopy())
815+
if (v.IsCopy())
821816
{
822-
int dist = (int)c.Current.BgraOrDistance;
817+
int dist = (int)v.BgraOrDistance;
823818
int transformedDist = DistanceToPlaneCode(xSize, dist);
824-
c.Current.BgraOrDistance = (uint)transformedDist;
819+
v = PixOrCopy.CreateCopy((uint)transformedDist, v.Len);
825820
}
826821
}
827822
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ public void Build(int xSize, int cacheBits, Vp8LBackwardRefs backwardRefs)
4040
using OwnedVp8LHistogram histogram = OwnedVp8LHistogram.Create(this.memoryAllocator, cacheBits);
4141

4242
// The following code is similar to HistogramCreate but converts the distance to plane code.
43-
for (int i = 0; i < backwardRefs.Refs.Count; i++)
43+
foreach (PixOrCopy v in backwardRefs)
4444
{
45-
histogram.AddSinglePixOrCopy(backwardRefs.Refs[i], true, xSize);
45+
histogram.AddSinglePixOrCopy(in v, true, xSize);
4646
}
4747

4848
ConvertPopulationCountTableToBitEstimates(histogram.NumCodes(), histogram.Literal, this.Literal);

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

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,11 @@ private static void HistogramBuild(
109109
{
110110
int x = 0, y = 0;
111111
int histoXSize = LosslessUtils.SubSampleSize(xSize, histoBits);
112-
using List<PixOrCopy>.Enumerator backwardRefsEnumerator = backwardRefs.Refs.GetEnumerator();
113-
while (backwardRefsEnumerator.MoveNext())
112+
113+
foreach (PixOrCopy v in backwardRefs)
114114
{
115-
PixOrCopy v = backwardRefsEnumerator.Current;
116115
int ix = ((y >> histoBits) * histoXSize) + (x >> histoBits);
117-
histograms[ix].AddSinglePixOrCopy(v, false);
116+
histograms[ix].AddSinglePixOrCopy(in v, false);
118117
x += v.Len;
119118
while (x >= xSize)
120119
{
@@ -217,7 +216,7 @@ private static void HistogramCombineEntropyBin(
217216
clusterMappings[idx] = (ushort)idx;
218217
}
219218

220-
List<int> indicesToRemove = new();
219+
List<int> indicesToRemove = [];
221220
Vp8LStreaks stats = new();
222221
Vp8LBitEntropy bitsEntropy = new();
223222
for (int idx = 0; idx < histograms.Count; idx++)
@@ -345,7 +344,7 @@ private static bool HistogramCombineStochastic(Vp8LHistogramSet histograms, int
345344

346345
// Priority list of histogram pairs. Its size impacts the quality of the compression and the speed:
347346
// the smaller the faster but the worse for the compression.
348-
List<HistogramPair> histoPriorityList = new();
347+
List<HistogramPair> histoPriorityList = [];
349348
const int maxSize = 9;
350349

351350
// Fill the initial mapping.
@@ -465,7 +464,7 @@ private static bool HistogramCombineStochastic(Vp8LHistogramSet histograms, int
465464
}
466465
}
467466

468-
HistoListUpdateHead(histoPriorityList, p);
467+
HistoListUpdateHead(histoPriorityList, p, j);
469468
j++;
470469
}
471470

@@ -480,7 +479,7 @@ private static void HistogramCombineGreedy(Vp8LHistogramSet histograms)
480479
int histoSize = histograms.Count(h => h != null);
481480

482481
// Priority list of histogram pairs.
483-
List<HistogramPair> histoPriorityList = new();
482+
List<HistogramPair> histoPriorityList = [];
484483
int maxSize = histoSize * histoSize;
485484
Vp8LStreaks stats = new();
486485
Vp8LBitEntropy bitsEntropy = new();
@@ -525,7 +524,7 @@ private static void HistogramCombineGreedy(Vp8LHistogramSet histograms)
525524
}
526525
else
527526
{
528-
HistoListUpdateHead(histoPriorityList, p);
527+
HistoListUpdateHead(histoPriorityList, p, i);
529528
i++;
530529
}
531530
}
@@ -647,7 +646,7 @@ private static double HistoPriorityListPush(
647646

648647
histoList.Add(pair);
649648

650-
HistoListUpdateHead(histoList, pair);
649+
HistoListUpdateHead(histoList, pair, histoList.Count - 1);
651650

652651
return pair.CostDiff;
653652
}
@@ -674,13 +673,11 @@ private static void HistoListUpdatePair(
674673
/// <summary>
675674
/// Check whether a pair in the list should be updated as head or not.
676675
/// </summary>
677-
private static void HistoListUpdateHead(List<HistogramPair> histoList, HistogramPair pair)
676+
private static void HistoListUpdateHead(List<HistogramPair> histoList, HistogramPair pair, int idx)
678677
{
679678
if (pair.CostDiff < histoList[0].CostDiff)
680679
{
681-
// Replace the best pair.
682-
int oldIdx = histoList.IndexOf(pair);
683-
histoList[oldIdx] = histoList[0];
680+
histoList[idx] = histoList[0];
684681
histoList[0] = pair;
685682
}
686683
}

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

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,24 @@
66
namespace SixLabors.ImageSharp.Formats.Webp.Lossless;
77

88
[DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")]
9-
internal sealed class PixOrCopy
9+
internal readonly struct PixOrCopy
1010
{
11-
public PixOrCopyMode Mode { get; set; }
12-
13-
public ushort Len { get; set; }
14-
15-
public uint BgraOrDistance { get; set; }
16-
17-
public static PixOrCopy CreateCacheIdx(int idx) =>
18-
new PixOrCopy
19-
{
20-
Mode = PixOrCopyMode.CacheIdx,
21-
BgraOrDistance = (uint)idx,
22-
Len = 1
23-
};
24-
25-
public static PixOrCopy CreateLiteral(uint bgra) =>
26-
new PixOrCopy
27-
{
28-
Mode = PixOrCopyMode.Literal,
29-
BgraOrDistance = bgra,
30-
Len = 1
31-
};
32-
33-
public static PixOrCopy CreateCopy(uint distance, ushort len) =>
34-
new PixOrCopy
11+
public readonly PixOrCopyMode Mode;
12+
public readonly ushort Len;
13+
public readonly uint BgraOrDistance;
14+
15+
private PixOrCopy(PixOrCopyMode mode, ushort len, uint bgraOrDistance)
3516
{
36-
Mode = PixOrCopyMode.Copy,
37-
BgraOrDistance = distance,
38-
Len = len
39-
};
17+
this.Mode = mode;
18+
this.Len = len;
19+
this.BgraOrDistance = bgraOrDistance;
20+
}
21+
22+
public static PixOrCopy CreateCacheIdx(int idx) => new(PixOrCopyMode.CacheIdx, 1, (uint)idx);
23+
24+
public static PixOrCopy CreateLiteral(uint bgra) => new(PixOrCopyMode.Literal, 1, bgra);
25+
26+
public static PixOrCopy CreateCopy(uint distance, ushort len) => new(PixOrCopyMode.Copy, len, distance);
4027

4128
public int Literal(int component) => (int)(this.BgraOrDistance >> (component * 8)) & 0xFF;
4229

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using System.Buffers;
5+
using SixLabors.ImageSharp.Memory;
6+
47
namespace SixLabors.ImageSharp.Formats.Webp.Lossless;
58

6-
internal class Vp8LBackwardRefs
9+
internal class Vp8LBackwardRefs : IDisposable
710
{
8-
public Vp8LBackwardRefs(int pixels) => this.Refs = new List<PixOrCopy>(pixels);
11+
private readonly IMemoryOwner<PixOrCopy> refs;
12+
private int count;
13+
14+
public Vp8LBackwardRefs(MemoryAllocator memoryAllocator, int pixels)
15+
{
16+
this.refs = memoryAllocator.Allocate<PixOrCopy>(pixels);
17+
this.count = 0;
18+
}
19+
20+
public void Add(PixOrCopy pixOrCopy) => this.refs.Memory.Span[this.count++] = pixOrCopy;
921

10-
/// <summary>
11-
/// Gets or sets the common block-size.
12-
/// </summary>
13-
public int BlockSize { get; set; }
22+
public void Clear() => this.count = 0;
1423

15-
/// <summary>
16-
/// Gets the backward references.
17-
/// </summary>
18-
public List<PixOrCopy> Refs { get; }
24+
public Span<PixOrCopy>.Enumerator GetEnumerator() => this.refs.Slice(0, this.count).GetEnumerator();
1925

20-
public void Add(PixOrCopy pixOrCopy) => this.Refs.Add(pixOrCopy);
26+
/// <inheritdoc/>
27+
public void Dispose() => this.refs.Dispose();
2128
}

0 commit comments

Comments
 (0)