Skip to content

Commit 013c29e

Browse files
authored
Merge pull request #1848 from SixLabors/bp/shanonsse
Add AVX2 versions of CombinedShannonEntropy
2 parents b67a8db + 9c95389 commit 013c29e

3 files changed

Lines changed: 219 additions & 21 deletions

File tree

src/ImageSharp/Common/Helpers/Numerics.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,26 @@ public static int ReduceSum(Vector128<int> accumulator)
820820
}
821821
}
822822

823+
/// <summary>
824+
/// Reduces elements of the vector into one sum.
825+
/// </summary>
826+
/// <param name="accumulator">The accumulator to reduce.</param>
827+
/// <returns>The sum of all elements.</returns>
828+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
829+
public static int ReduceSum(Vector256<int> accumulator)
830+
{
831+
// Add upper lane to lower lane.
832+
Vector128<int> vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper());
833+
834+
// Add odd to even.
835+
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_11_01_01));
836+
837+
// Add high to low.
838+
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10));
839+
840+
return Sse2.ConvertToInt32(vsum);
841+
}
842+
823843
/// <summary>
824844
/// Reduces even elements of the vector into one sum.
825845
/// </summary>

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

Lines changed: 175 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5+
using System.Numerics;
56
using System.Runtime.CompilerServices;
67
using System.Runtime.InteropServices;
78
using SixLabors.ImageSharp.Memory;
@@ -761,28 +762,184 @@ public static void BundleColorMap(Span<byte> row, int width, int xBits, Span<uin
761762
/// <returns>Shanon entropy.</returns>
762763
public static float CombinedShannonEntropy(Span<int> x, Span<int> y)
763764
{
764-
double retVal = 0.0d;
765-
uint sumX = 0, sumXY = 0;
766-
for (int i = 0; i < 256; i++)
765+
#if SUPPORTS_RUNTIME_INTRINSICS
766+
if (Avx2.IsSupported)
767767
{
768-
uint xi = (uint)x[i];
769-
if (xi != 0)
768+
double retVal = 0.0d;
769+
Vector256<int> tmp = Vector256<int>.Zero; // has the size of the scratch space of sizeof(int) * 8
770+
ref int xRef = ref MemoryMarshal.GetReference(x);
771+
ref int yRef = ref MemoryMarshal.GetReference(y);
772+
Vector256<int> sumXY256 = Vector256<int>.Zero;
773+
Vector256<int> sumX256 = Vector256<int>.Zero;
774+
ref int tmpRef = ref Unsafe.As<Vector256<int>, int>(ref tmp);
775+
for (nint i = 0; i < 256; i += 8)
770776
{
771-
uint xy = xi + (uint)y[i];
772-
sumX += xi;
773-
retVal -= FastSLog2(xi);
774-
sumXY += xy;
775-
retVal -= FastSLog2(xy);
777+
Vector256<int> xVec = Unsafe.As<int, Vector256<int>>(ref Unsafe.Add(ref xRef, i));
778+
Vector256<int> yVec = Unsafe.As<int, Vector256<int>>(ref Unsafe.Add(ref yRef, i));
779+
780+
// Check if any X is non-zero: this actually provides a speedup as X is usually sparse.
781+
int mask = Avx2.MoveMask(Avx2.CompareEqual(xVec, Vector256<int>.Zero).AsByte());
782+
if (mask != -1)
783+
{
784+
Vector256<int> xy256 = Avx2.Add(xVec, yVec);
785+
sumXY256 = Avx2.Add(sumXY256, xy256);
786+
sumX256 = Avx2.Add(sumX256, xVec);
787+
788+
// Analyze the different X + Y.
789+
Unsafe.As<int, Vector256<int>>(ref tmpRef) = xy256;
790+
if (tmpRef != 0)
791+
{
792+
retVal -= FastSLog2((uint)tmpRef);
793+
if (Unsafe.Add(ref xRef, i) != 0)
794+
{
795+
retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i));
796+
}
797+
}
798+
799+
if (Unsafe.Add(ref tmpRef, 1) != 0)
800+
{
801+
retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 1));
802+
if (Unsafe.Add(ref xRef, i + 1) != 0)
803+
{
804+
retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 1));
805+
}
806+
}
807+
808+
if (Unsafe.Add(ref tmpRef, 2) != 0)
809+
{
810+
retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 2));
811+
if (Unsafe.Add(ref xRef, i + 2) != 0)
812+
{
813+
retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 2));
814+
}
815+
}
816+
817+
if (Unsafe.Add(ref tmpRef, 3) != 0)
818+
{
819+
retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 3));
820+
if (Unsafe.Add(ref xRef, i + 3) != 0)
821+
{
822+
retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 3));
823+
}
824+
}
825+
826+
if (Unsafe.Add(ref tmpRef, 4) != 0)
827+
{
828+
retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 4));
829+
if (Unsafe.Add(ref xRef, i + 4) != 0)
830+
{
831+
retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 4));
832+
}
833+
}
834+
835+
if (Unsafe.Add(ref tmpRef, 5) != 0)
836+
{
837+
retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 5));
838+
if (Unsafe.Add(ref xRef, i + 5) != 0)
839+
{
840+
retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 5));
841+
}
842+
}
843+
844+
if (Unsafe.Add(ref tmpRef, 6) != 0)
845+
{
846+
retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 6));
847+
if (Unsafe.Add(ref xRef, i + 6) != 0)
848+
{
849+
retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 6));
850+
}
851+
}
852+
853+
if (Unsafe.Add(ref tmpRef, 7) != 0)
854+
{
855+
retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 7));
856+
if (Unsafe.Add(ref xRef, i + 7) != 0)
857+
{
858+
retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 7));
859+
}
860+
}
861+
}
862+
else
863+
{
864+
// X is fully 0, so only deal with Y.
865+
sumXY256 = Avx2.Add(sumXY256, yVec);
866+
867+
if (Unsafe.Add(ref yRef, i) != 0)
868+
{
869+
retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i));
870+
}
871+
872+
if (Unsafe.Add(ref yRef, i + 1) != 0)
873+
{
874+
retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 1));
875+
}
876+
877+
if (Unsafe.Add(ref yRef, i + 2) != 0)
878+
{
879+
retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 2));
880+
}
881+
882+
if (Unsafe.Add(ref yRef, i + 3) != 0)
883+
{
884+
retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 3));
885+
}
886+
887+
if (Unsafe.Add(ref yRef, i + 4) != 0)
888+
{
889+
retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 4));
890+
}
891+
892+
if (Unsafe.Add(ref yRef, i + 5) != 0)
893+
{
894+
retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 5));
895+
}
896+
897+
if (Unsafe.Add(ref yRef, i + 6) != 0)
898+
{
899+
retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 6));
900+
}
901+
902+
if (Unsafe.Add(ref yRef, i + 7) != 0)
903+
{
904+
retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 7));
905+
}
906+
}
776907
}
777-
else if (y[i] != 0)
908+
909+
// Sum up sumX256 to get sumX and sum up sumXY256 to get sumXY.
910+
int sumX = Numerics.ReduceSum(sumX256);
911+
int sumXY = Numerics.ReduceSum(sumXY256);
912+
913+
retVal += FastSLog2((uint)sumX) + FastSLog2((uint)sumXY);
914+
915+
return (float)retVal;
916+
}
917+
else
918+
#endif
919+
{
920+
double retVal = 0.0d;
921+
uint sumX = 0, sumXY = 0;
922+
for (int i = 0; i < 256; i++)
778923
{
779-
sumXY += (uint)y[i];
780-
retVal -= FastSLog2((uint)y[i]);
924+
uint xi = (uint)x[i];
925+
if (xi != 0)
926+
{
927+
uint xy = xi + (uint)y[i];
928+
sumX += xi;
929+
retVal -= FastSLog2(xi);
930+
sumXY += xy;
931+
retVal -= FastSLog2(xy);
932+
}
933+
else if (y[i] != 0)
934+
{
935+
sumXY += (uint)y[i];
936+
retVal -= FastSLog2((uint)y[i]);
937+
}
781938
}
782-
}
783939

784-
retVal += FastSLog2(sumX) + FastSLog2(sumXY);
785-
return (float)retVal;
940+
retVal += FastSLog2(sumX) + FastSLog2(sumXY);
941+
return (float)retVal;
942+
}
786943
}
787944

788945
[MethodImpl(InliningOptions.ShortMethod)]
@@ -838,6 +995,7 @@ public static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m)
838995
private static float FastSLog2Slow(uint v)
839996
{
840997
DebugGuard.MustBeGreaterThanOrEqualTo<uint>(v, LogLookupIdxMax, nameof(v));
998+
841999
if (v < ApproxLogWithCorrectionMax)
8421000
{
8431001
int logCnt = 0;
@@ -867,7 +1025,7 @@ private static float FastSLog2Slow(uint v)
8671025

8681026
private static float FastLog2Slow(uint v)
8691027
{
870-
Guard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v));
1028+
DebugGuard.MustBeGreaterThanOrEqualTo<uint>(v, LogLookupIdxMax, nameof(v));
8711029

8721030
if (v < ApproxLogWithCorrectionMax)
8731031
{

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

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
1010
[Trait("Format", "Webp")]
1111
public class LosslessUtilsTests
1212
{
13+
private static void RunCombinedShannonEntropyTest()
14+
{
15+
int[] x = { 3, 5, 2, 5, 3, 1, 2, 2, 3, 3, 1, 2, 1, 2, 1, 1, 0, 0, 0, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 1, 1, 0, 0, 2, 1, 1, 0, 3, 1, 2, 3, 2, 3 };
16+
int[] y = { 11, 12, 8, 3, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 2, 1, 1, 2, 4, 6, 4 };
17+
float expected = 884.7585f;
18+
19+
float actual = LosslessUtils.CombinedShannonEntropy(x, y);
20+
21+
Assert.Equal(expected, actual, 5);
22+
}
23+
1324
private static void RunSubtractGreenTest()
1425
{
1526
uint[] pixelData =
@@ -193,6 +204,9 @@ private static void RunPredictor13Test()
193204
}
194205
}
195206

207+
[Fact]
208+
public void CombinedShannonEntropy_Works() => RunCombinedShannonEntropyTest();
209+
196210
[Fact]
197211
public void Predictor11_Works() => RunPredictor11Test();
198212

@@ -216,6 +230,12 @@ private static void RunPredictor13Test()
216230

217231
#if SUPPORTS_RUNTIME_INTRINSICS
218232

233+
[Fact]
234+
public void CombinedShannonEntropy_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCombinedShannonEntropyTest, HwIntrinsics.AllowAll);
235+
236+
[Fact]
237+
public void CombinedShannonEntropy_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCombinedShannonEntropyTest, HwIntrinsics.DisableAVX2);
238+
219239
[Fact]
220240
public void Predictor11_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.AllowAll);
221241

@@ -238,19 +258,19 @@ private static void RunPredictor13Test()
238258
public void SubtractGreen_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.AllowAll);
239259

240260
[Fact]
241-
public void SubtractGreen_WithoutAvx_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX);
261+
public void SubtractGreen_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2);
242262

243263
[Fact]
244-
public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSSE3);
264+
public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3);
245265

246266
[Fact]
247267
public void AddGreenToBlueAndRed_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.AllowAll);
248268

249269
[Fact]
250-
public void AddGreenToBlueAndRed_WithoutAvx_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX);
270+
public void AddGreenToBlueAndRed_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2);
251271

252272
[Fact]
253-
public void AddGreenToBlueAndRed_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3);
273+
public void AddGreenToBlueAndRed_WithoutAVX2OrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3);
254274

255275
[Fact]
256276
public void TransformColor_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.AllowAll);

0 commit comments

Comments
 (0)