Browse Source

Inline resize kernel normalization

Remove the `Numerics.Normalize` wrapper and call `TensorPrimitives.Divide` directly when normalizing resize kernels. This also drops the now-unused normalization test coverage and the redundant benchmark-only `System.Numerics.Tensors` package reference for net10.0.
pull/3179/head
James Jackson-South 1 week ago
parent
commit
d21316993b
  1. 11
      src/ImageSharp/Common/Helpers/Numerics.cs
  2. 3
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  3. 1
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  4. 56
      tests/ImageSharp.Tests/Common/NumericsTests.cs

11
src/ImageSharp/Common/Helpers/Numerics.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Numerics; using System.Numerics;
@ -1033,13 +1033,4 @@ internal static class Numerics
public static nuint Vector512Count<TVector>(int length) public static nuint Vector512Count<TVector>(int length)
where TVector : struct where TVector : struct
=> (uint)length / (uint)Vector512<TVector>.Count; => (uint)length / (uint)Vector512<TVector>.Count;
/// <summary>
/// Normalizes the values in a given <see cref="Span{T}"/>.
/// </summary>
/// <param name="span">The sequence of <see cref="float"/> values to normalize.</param>
/// <param name="sum">The sum of the values in <paramref name="span"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Normalize(Span<float> span, float sum)
=> TensorPrimitives.Divide(span, sum, span);
} }

3
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs

@ -3,6 +3,7 @@
using System.Buffers; using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics.Tensors;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -243,7 +244,7 @@ internal partial class ResizeKernelMap : IDisposable
// Normalize, best to do it here rather than in the pixel loop later on. // Normalize, best to do it here rather than in the pixel loop later on.
if (sum > 0) if (sum > 0)
{ {
Numerics.Normalize(kernelValues, sum); TensorPrimitives.Divide(kernelValues, sum, kernelValues);
} }
kernel.FillOrCopyAndExpand(kernelValues); kernel.FillOrCopyAndExpand(kernelValues);

1
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -69,7 +69,6 @@
<PackageReference Include="SharpZipLib" /> <PackageReference Include="SharpZipLib" />
<PackageReference Include="SkiaSharp" /> <PackageReference Include="SkiaSharp" />
<PackageReference Include="System.Drawing.Common" /> <PackageReference Include="System.Drawing.Common" />
<PackageReference Include="System.Numerics.Tensors" Version="10.0.0" Condition="'$(TargetFramework)' == 'net10.0'" />
</ItemGroup> </ItemGroup>
<!-- Exclude benchmarks using internals, in case of unsigned benchmark execution: --> <!-- Exclude benchmarks using internals, in case of unsigned benchmark execution: -->

56
tests/ImageSharp.Tests/Common/NumericsTests.cs

@ -5,42 +5,12 @@ namespace SixLabors.ImageSharp.Tests.Common;
public class NumericsTests public class NumericsTests
{ {
private static readonly int[] NormalizeSpanLengthValues =
[
0,
1,
3,
4,
5,
7,
8,
9,
15,
16,
17,
31,
32,
33,
63,
64,
65,
127,
128,
129,
2048
];
private ITestOutputHelper Output { get; } private ITestOutputHelper Output { get; }
public NumericsTests(ITestOutputHelper output) => this.Output = output; public NumericsTests(ITestOutputHelper output) => this.Output = output;
public static TheoryData<int> IsOutOfRangeTestData = new() { int.MinValue, -1, 0, 1, 6, 7, 8, 91, 92, 93, int.MaxValue }; public static TheoryData<int> IsOutOfRangeTestData = new() { int.MinValue, -1, 0, 1, 6, 7, 8, 91, 92, 93, int.MaxValue };
/// <summary>
/// Gets lengths that exercise scalar execution and the supported SIMD widths.
/// </summary>
public static TheoryData<int> NormalizeSpanLengths => new(NormalizeSpanLengthValues);
private static uint DivideCeil_ReferenceImplementation(uint value, uint divisor) => (uint)MathF.Ceiling((float)value / divisor); private static uint DivideCeil_ReferenceImplementation(uint value, uint divisor) => (uint)MathF.Ceiling((float)value / divisor);
[Fact] [Fact]
@ -84,30 +54,4 @@ public class NumericsTests
Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})"); Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})");
} }
/// <summary>
/// Verifies that normalization divides every element by the supplied sum.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(NormalizeSpanLengths))]
public void NormalizeMatchesScalarFormula(int length)
{
float[] actual = new float[length];
float[] expected = new float[length];
for (int i = 0; i < actual.Length; i++)
{
actual[i] = (i + 1) * 0.125F;
expected[i] = actual[i] / 7.5F;
}
Numerics.Normalize(actual, 7.5F);
Assert.Equal(expected.Length, actual.Length);
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(BitConverter.SingleToInt32Bits(expected[i]), BitConverter.SingleToInt32Bits(actual[i]));
}
}
} }

Loading…
Cancel
Save