Browse Source

AVX2 optimized Block8x8 -> Block8x8F conversion

pull/768/head
Anton Firszov 7 years ago
parent
commit
7f113ab00a
  1. 14
      src/ImageSharp/Common/Helpers/DebugGuard.cs
  2. 2
      src/ImageSharp/Common/Helpers/InliningOptions.cs
  3. 14
      src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
  4. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
  5. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
  6. 45
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  7. 21
      tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
  8. 42
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  9. 4
      tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
  10. 7
      tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs
  11. 2
      tests/ImageSharp.Tests/ProfilingBenchmarks.cs
  12. 11
      tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs

14
src/ImageSharp/Common/Helpers/DebugGuard.cs

@ -163,6 +163,20 @@ namespace SixLabors.ImageSharp
}
}
/// <summary>
/// Verifies whether a specific condition is met, throwing an exception if it's false.
/// </summary>
/// <param name="target">The condition</param>
/// <param name="message">The error message</param>
[Conditional("DEBUG")]
public static void IsTrue(bool target, string message)
{
if (!target)
{
throw new InvalidOperationException(message);
}
}
/// <summary>
/// Verifies, that the method parameter with specified target value is false
/// and throws an exception if it is found to be so.

2
src/ImageSharp/Common/Helpers/InliningOptions.cs

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
// Uncomment this for verbose profiler results:
// #define PROFILING
#define PROFILING
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp

14
src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs

@ -25,6 +25,20 @@ namespace SixLabors.ImageSharp
false;
#endif
/// <summary>
/// Widen and convert a vector of <see cref="short"/> values into 2 vectors of <see cref="float"/>-s.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void ConvertToSingle(
Vector<short> source,
out Vector<float> dest1,
out Vector<float> dest2)
{
Vector.Widen(source, out Vector<int> i1, out Vector<int> i2);
dest1 = Vector.ConvertToSingle(i1);
dest2 = Vector.ConvertToSingle(i2);
}
/// <summary>
/// <see cref="BulkConvertByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs

@ -154,7 +154,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <summary>
/// Fill the block from 'source' doing short -> float conversion.
/// </summary>
public void LoadFrom(ref Block8x8 source)
public void LoadFromInt16Scalar(ref Block8x8 source)
{
ref short selfRef = ref Unsafe.As<Block8x8, short>(ref source);

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt

@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <summary>
/// Fill the block from 'source' doing short -> float conversion.
/// </summary>
public void LoadFrom(ref Block8x8 source)
public void LoadFromInt16Scalar(ref Block8x8 source)
{
ref short selfRef = ref Unsafe.As<Block8x8, short>(ref source);

45
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -493,6 +493,51 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
}
[MethodImpl(InliningOptions.ShortMethod)]
public void LoadFrom(ref Block8x8 source)
{
#if SUPPORTS_EXTENDED_INTRINSICS
if (SimdUtils.IsAvx2CompatibleArchitecture)
{
this.LoadFromInt16ExtendedAvx2(ref source);
return;
}
#endif
this.LoadFromInt16Scalar(ref source);
}
/// <summary>
/// Loads values from <paramref name="source"/> using extended AVX2 intrinsics.
/// </summary>
/// <param name="source">The source <see cref="Block8x8"/></param>
public void LoadFromInt16ExtendedAvx2(ref Block8x8 source)
{
DebugGuard.IsTrue(
SimdUtils.IsAvx2CompatibleArchitecture,
"LoadFromUInt16ExtendedAvx2 only works on AVX2 compatible architecture!");
ref Vector<short> sRef = ref Unsafe.As<Block8x8, Vector<short>>(ref source);
ref Vector<float> dRef = ref Unsafe.As<Block8x8F, Vector<float>>(ref this);
// Vector<ushort>.Count == 16 on AVX2
// We can process 2 block rows in a single step
SimdUtils.ExtendedIntrinsics.ConvertToSingle(sRef, out Vector<float> top, out Vector<float> bottom);
dRef = top;
Unsafe.Add(ref dRef, 1) = bottom;
SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 1), out top, out bottom);
Unsafe.Add(ref dRef, 2) = top;
Unsafe.Add(ref dRef, 3) = bottom;
SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 2), out top, out bottom);
Unsafe.Add(ref dRef, 4) = top;
Unsafe.Add(ref dRef, 5) = bottom;
SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 3), out top, out bottom);
Unsafe.Add(ref dRef, 6) = top;
Unsafe.Add(ref dRef, 7) = bottom;
}
/// <inheritdoc />
public override string ToString()
{

21
tests/ImageSharp.Tests/Common/SimdUtilsTests.cs

@ -257,6 +257,27 @@ namespace SixLabors.ImageSharp.Tests.Common
);
}
[Theory]
[InlineData(1234)]
public void ExtendedIntrinsics_ConvertToSingle(short scale)
{
int n = Vector<float>.Count;
short[] sData = new Random(scale).GenerateRandomInt16Array(2 * n, (short)-scale, scale);
float[] fData = sData.Select(u => (float)u).ToArray();
var source = new Vector<short>(sData);
var expected1 = new Vector<float>(fData, 0);
var expected2 = new Vector<float>(fData, n);
// Act:
SimdUtils.ExtendedIntrinsics.ConvertToSingle(source, out Vector<float> actual1, out Vector<float> actual2);
// Assert:
Assert.Equal(expected1, actual1);
Assert.Equal(expected2, actual2);
}
[Theory]
[MemberData(nameof(ArbitraryArraySizes))]
public void BulkConvertNormalizedFloatToByteClampOverflows(int count)

42
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs

@ -408,5 +408,47 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(original[i] * 42f, actual[i]);
}
}
[Fact]
public void LoadFromUInt16Scalar()
{
if (this.SkipOnNonAvx2Runner())
{
return;
}
short[] data = Create8x8ShortData();
var source = new Block8x8(data);
Block8x8F dest = default;
dest.LoadFromInt16Scalar(ref source);
for (int i = 0; i < Block8x8F.Size; i++)
{
Assert.Equal((float)data[i], dest[i]);
}
}
[Fact]
public void LoadFromUInt16ExtendedAvx2()
{
if (this.SkipOnNonAvx2Runner())
{
return;
}
short[] data = Create8x8ShortData();
var source = new Block8x8(data);
Block8x8F dest = default;
dest.LoadFromInt16ExtendedAvx2(ref source);
for (int i = 0; i < Block8x8F.Size; i++)
{
Assert.Equal((float)data[i], dest[i]);
}
}
}
}

4
tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs

@ -32,8 +32,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Baseline.Jpeg444,
};
[Theory] // Benchmark, enable manually
[MemberData(nameof(DecodeJpegData))]
//[Theory] // Benchmark, enable manually
//[MemberData(nameof(DecodeJpegData))]
public void DecodeJpeg(string fileName)
{
this.DecodeJpegBenchmarkImpl(fileName, new JpegDecoder());

7
tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs

@ -58,7 +58,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
{
for (int j = 0; j < 8; j++)
{
result[i * 8 + j] = (short)(i * 10 + j);
short val = (short)(i * 10 + j);
if ((i + j) % 2 == 0)
{
val *= -1;
}
result[i * 8 + j] = val;
}
}
return result;

2
tests/ImageSharp.Tests/ProfilingBenchmarks.cs

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests
public class ProfilingBenchmarks : MeasureFixture
{
public const string SkipProfilingTests =
#if false
#if true
null;
#else
"Profiling benchmark, enable manually!";

11
tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs

@ -88,6 +88,17 @@ namespace SixLabors.ImageSharp.Tests
return values;
}
public static short[] GenerateRandomInt16Array(this Random rnd, int length, short minVal, short maxVal)
{
short[] values = new short[length];
for (int i = 0; i < values.Length; i++)
{
values[i] = (short)rnd.Next(minVal, maxVal);
}
return values;
}
private static float GetRandomFloat(this Random rnd, float minVal, float maxVal) => ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal;
}
}
Loading…
Cancel
Save