Skip to content

Commit f7d1a9e

Browse files
Merge pull request #2947 from SixLabors/js/v3-backport-2940
V3 Backport : Reduce the number of memory allocations in lossless WebP encoder
2 parents abf46b7 + 57f58fe commit f7d1a9e

File tree

8 files changed

+71
-89
lines changed

8 files changed

+71
-89
lines changed

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

Lines changed: 10 additions & 15 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);
@@ -780,19 +779,16 @@ private static void BackwardRefsWithLocalCache(ReadOnlySpan<uint> bgra, int cach
780779
{
781780
int pixelIndex = 0;
782781
ColorCache colorCache = new ColorCache(cacheBits);
783-
for (int idx = 0; idx < refs.Refs.Count; idx++)
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: 8 additions & 11 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
{
@@ -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

@@ -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
}

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,9 @@ public Vp8LEncoder(
137137
this.Refs = new Vp8LBackwardRefs[3];
138138
this.HashChain = new Vp8LHashChain(memoryAllocator, pixelCount);
139139

140-
// We round the block size up, so we're guaranteed to have at most MaxRefsBlockPerImage blocks used:
141-
int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1;
142140
for (int i = 0; i < this.Refs.Length; i++)
143141
{
144-
this.Refs[i] = new Vp8LBackwardRefs(pixelCount)
145-
{
146-
BlockSize = refsBlockSize < MinBlockSize ? MinBlockSize : refsBlockSize
147-
};
142+
this.Refs[i] = new Vp8LBackwardRefs(memoryAllocator, pixelCount);
148143
}
149144
}
150145

@@ -1071,9 +1066,8 @@ private void StoreImageToBitMask(
10711066
int histogramIx = histogramSymbols[0];
10721067
Span<HuffmanTreeCode> codes = huffmanCodes.AsSpan(5 * histogramIx);
10731068

1074-
for (int i = 0; i < backwardRefs.Refs.Count; i++)
1069+
foreach (PixOrCopy v in backwardRefs)
10751070
{
1076-
PixOrCopy v = backwardRefs.Refs[i];
10771071
if (tileX != (x & tileMask) || tileY != (y & tileMask))
10781072
{
10791073
tileX = x & tileMask;
@@ -1907,9 +1901,9 @@ public void AllocateTransformBuffer(int width, int height)
19071901
/// </summary>
19081902
public void ClearRefs()
19091903
{
1910-
foreach (Vp8LBackwardRefs t in this.Refs)
1904+
foreach (Vp8LBackwardRefs refs in this.Refs)
19111905
{
1912-
t.Refs.Clear();
1906+
refs.Clear();
19131907
}
19141908
}
19151909

@@ -1921,6 +1915,12 @@ public void Dispose()
19211915
this.BgraScratch?.Dispose();
19221916
this.Palette.Dispose();
19231917
this.TransformData?.Dispose();
1918+
1919+
foreach (Vp8LBackwardRefs refs in this.Refs)
1920+
{
1921+
refs.Dispose();
1922+
}
1923+
19241924
this.HashChain.Dispose();
19251925
}
19261926

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ public void Clear()
138138
/// <param name="refs">The backward references.</param>
139139
public void StoreRefs(Vp8LBackwardRefs refs)
140140
{
141-
for (int i = 0; i < refs.Refs.Count; i++)
141+
foreach (PixOrCopy v in refs)
142142
{
143-
this.AddSinglePixOrCopy(refs.Refs[i], false);
143+
this.AddSinglePixOrCopy(in v, false);
144144
}
145145
}
146146

@@ -150,7 +150,7 @@ public void StoreRefs(Vp8LBackwardRefs refs)
150150
/// <param name="v">The token to add.</param>
151151
/// <param name="useDistanceModifier">Indicates whether to use the distance modifier.</param>
152152
/// <param name="xSize">xSize is only used when useDistanceModifier is true.</param>
153-
public void AddSinglePixOrCopy(PixOrCopy v, bool useDistanceModifier, int xSize = 0)
153+
public void AddSinglePixOrCopy(in PixOrCopy v, bool useDistanceModifier, int xSize = 0)
154154
{
155155
if (v.IsLiteral())
156156
{

tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,14 @@ private static void RunAddVectorTest()
6666
// All remaining values are expected to be zero.
6767
literals.AsSpan().CopyTo(expectedLiterals);
6868

69-
Vp8LBackwardRefs backwardRefs = new(pixelData.Length);
69+
MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator;
70+
71+
using Vp8LBackwardRefs backwardRefs = new(memoryAllocator, pixelData.Length);
7072
for (int i = 0; i < pixelData.Length; i++)
7173
{
72-
backwardRefs.Add(new PixOrCopy()
73-
{
74-
BgraOrDistance = pixelData[i],
75-
Len = 1,
76-
Mode = PixOrCopyMode.Literal
77-
});
74+
backwardRefs.Add(PixOrCopy.CreateLiteral(pixelData[i]));
7875
}
7976

80-
MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator;
8177
using OwnedVp8LHistogram histogram0 = OwnedVp8LHistogram.Create(memoryAllocator, backwardRefs, 3);
8278
using OwnedVp8LHistogram histogram1 = OwnedVp8LHistogram.Create(memoryAllocator, backwardRefs, 3);
8379
for (int i = 0; i < 5; i++)

0 commit comments

Comments
 (0)