Browse Source

speedup Block8x8F.RoundInplace()

pull/299/head
Anton Firszov 9 years ago
parent
commit
3e73bd8cf1
  1. 45
      src/ImageSharp/Common/Extensions/SimdUtils.cs
  2. 11
      src/ImageSharp/Common/Extensions/Vector4Extensions.cs
  3. 47
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
  4. 3
      tests/ImageSharp.Benchmarks/General/Block8x8F_DivideRound.cs
  5. 67
      tests/ImageSharp.Benchmarks/General/Block8x8F_Round.cs
  6. 119
      tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
  7. 23
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
  8. 30
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

45
src/ImageSharp/Common/Extensions/SimdUtils.cs

@ -0,0 +1,45 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Various extension and utility methods for <see cref="Vector4"/> and <see cref="Vector{T}"/> utilizing SIMD capabilities
/// </summary>
internal static class SimdUtils
{
/// <summary>
/// Transform all scalars in 'v' in a way that converting them to <see cref="int"/> would have rounding semantics.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector4 PseudoRound(this Vector4 v)
{
var sign = Vector4.Clamp(v, new Vector4(-1), new Vector4(1));
return v + (sign * 0.5f);
}
/// <summary>
/// Rounds all values in 'v' to the nearest integer following <see cref="MidpointRounding.ToEven"/> semantics.
/// Source:
/// <see>
/// <cref>https://github.com/tmpvar/voxviz/blob/master/deps/glm/glm/simd/common.h#L110</cref>
/// </see>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector<float> FastRound(this Vector<float> x)
{
Vector<int> magic0 = new Vector<int>(-2147483648); // 0x80000000
Vector<float> sgn0 = Vector.AsVectorSingle(magic0);
Vector<float> and0 = Vector.BitwiseAnd(sgn0, x);
Vector<float> or0 = Vector.BitwiseOr(and0, new Vector<float>(8388608.0f));
Vector<float> add0 = Vector.Add(x, or0);
Vector<float> sub0 = Vector.Subtract(add0, or0);
return sub0;
}
}
}

11
src/ImageSharp/Common/Extensions/Vector4Extensions.cs

@ -79,16 +79,5 @@ namespace SixLabors.ImageSharp
return MathF.Pow((signal + 0.055F) / 1.055F, 2.4F);
}
/// <summary>
/// Transform all scalars in 'v' in a way that converting them to <see cref="int"/> would have rounding semantics.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector4 PseudoRound(this Vector4 v)
{
var sign = Vector4.Clamp(v, new Vector4(-1), new Vector4(1));
return v + (sign * 0.5f);
}
}
}

47
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs

@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Memory;
// ReSharper disable InconsistentNaming
@ -609,8 +610,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
return result;
}
// TODO: Optimize this!
public void RoundInplace()
{
if (Vector<float>.Count == 8)
{
ref Vector<float> row0 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V0L);
row0 = row0.FastRound();
ref Vector<float> row1 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V1L);
row1 = row1.FastRound();
ref Vector<float> row2 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V2L);
row2 = row2.FastRound();
ref Vector<float> row3 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V3L);
row3 = row3.FastRound();
ref Vector<float> row4 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V4L);
row4 = row4.FastRound();
ref Vector<float> row5 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V5L);
row5 = row5.FastRound();
ref Vector<float> row6 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V6L);
row6 = row6.FastRound();
ref Vector<float> row7 = ref Unsafe.As<Vector4, Vector<float>>(ref this.V7L);
row7 = row7.FastRound();
}
else
{
this.RoundInplaceSlow();
}
}
private void RoundInplaceSlow()
{
for (int i = 0; i < Size; i++)
{
@ -618,6 +645,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
}
}
/// <inheritdoc />
public override string ToString()
{
var bld = new StringBuilder();
bld.Append('[');
for (int i = 0; i < Size; i++)
{
bld.Append(this[i]);
if (i < Size - 1)
{
bld.Append(',');
}
}
bld.Append(']');
return bld.ToString();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{

3
tests/ImageSharp.Benchmarks/General/RoundSinglePrecisionBlocks.cs → tests/ImageSharp.Benchmarks/General/Block8x8F_DivideRound.cs

@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.General
{
@ -15,7 +16,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General
/// - Divide each float pair, round the result
/// - Iterate through all rounded values as int-s
/// </summary>
public unsafe class RoundSinglePrecisionBlocks
public unsafe class Block8x8F_DivideRound
{
private const int ExecutionCount = 5; // Added this to reduce the effect of copying the blocks
private static readonly Vector4 MinusOne = new Vector4(-1);

67
tests/ImageSharp.Benchmarks/General/Block8x8F_Round.cs

@ -0,0 +1,67 @@
// ReSharper disable InconsistentNaming
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
namespace SixLabors.ImageSharp.Benchmarks.General
{
public class Block8x8F_Round
{
private Block8x8F block = default(Block8x8F);
[GlobalSetup]
public void Setup()
{
if (Vector<float>.Count != 8)
{
throw new NotSupportedException("Vector<float>.Count != 8");
}
for (int i = 0; i < Block8x8F.Size; i++)
{
this.block[i] = i * 44.8f;
}
}
[Benchmark(Baseline = true)]
public void ScalarRound()
{
ref float b = ref Unsafe.As<Block8x8F, float>(ref this.block);
for (int i = 0; i < Block8x8F.Size; i++)
{
ref float v = ref Unsafe.Add(ref b, i);
v = MathF.Round(v);
}
}
[Benchmark]
public void SimdRound()
{
ref Block8x8F b = ref this.block;
ref Vector<float> row0 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V0L);
row0 = SimdUtils.FastRound(row0);
ref Vector<float> row1 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V1L);
row1 = SimdUtils.FastRound(row1);
ref Vector<float> row2 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V2L);
row2 = SimdUtils.FastRound(row2);
ref Vector<float> row3 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V3L);
row3 = SimdUtils.FastRound(row3);
ref Vector<float> row4 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V4L);
row4 = SimdUtils.FastRound(row4);
ref Vector<float> row5 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V5L);
row5 = SimdUtils.FastRound(row5);
ref Vector<float> row6 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V6L);
row6 = SimdUtils.FastRound(row6);
ref Vector<float> row7 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V7L);
row7 = SimdUtils.FastRound(row7);
}
}
}

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

@ -0,0 +1,119 @@
using System;
using System.Numerics;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Common
{
using Xunit.Abstractions;
public class SimdUtilsTests
{
private ITestOutputHelper Output { get; }
public SimdUtilsTests(ITestOutputHelper output)
{
this.Output = output;
}
private static int R(float f) => (int)MathF.Round(f, MidpointRounding.AwayFromZero);
private static int Re(float f) => (int)MathF.Round(f, MidpointRounding.ToEven);
// TODO: Move this to a proper test class!
[Theory]
[InlineData(0.32, 54.5, -3.5, -4.1)]
[InlineData(5.3, 536.4, 4.5, 8.1)]
public void PseudoRound(float x, float y, float z, float w)
{
var v = new Vector4(x, y, z, w);
Vector4 actual = v.PseudoRound();
Assert.Equal(
R(v.X),
(int)actual.X
);
Assert.Equal(
R(v.Y),
(int)actual.Y
);
Assert.Equal(
R(v.Z),
(int)actual.Z
);
Assert.Equal(
R(v.W),
(int)actual.W
);
}
private static Vector<float> CreateExactTestVector1()
{
float[] data = new float[Vector<float>.Count];
data[0] = 0.1f;
data[1] = 0.4f;
data[2] = 0.5f;
data[3] = 0.9f;
for (int i = 4; i < Vector<float>.Count; i++)
{
data[i] = data[i - 4] + 100f;
}
return new Vector<float>(data);
}
private static Vector<float> CreateRandomTestVector(int seed, float scale)
{
float[] data = new float[Vector<float>.Count];
Random rnd = new Random();
for (int i = 0; i < Vector<float>.Count; i++)
{
float v = (float)rnd.NextDouble() - 0.5f;
v *= 2 * scale;
data[i] = v;
}
return new Vector<float>(data);
}
[Fact]
public void Round()
{
Vector<float> v = CreateExactTestVector1();
Vector<float> r = v.FastRound();
this.Output.WriteLine(r.ToString());
AssertEvenRoundIsCorrect(r, v);
}
[Theory]
[InlineData(1, 1f)]
[InlineData(1, 10f)]
[InlineData(1, 1000f)]
[InlineData(42, 1f)]
[InlineData(42, 10f)]
[InlineData(42, 1000f)]
public void Round_RandomValues(int seed, float scale)
{
Vector<float> v = CreateRandomTestVector(seed, scale);
Vector<float> r = v.FastRound();
this.Output.WriteLine(v.ToString());
this.Output.WriteLine(r.ToString());
AssertEvenRoundIsCorrect(r, v);
}
private static void AssertEvenRoundIsCorrect(Vector<float> r, Vector<float> v)
{
for (int i = 0; i < Vector<float>.Count; i++)
{
int actual = (int)r[i];
int expected = Re(v[i]);
Assert.Equal(expected, actual);
}
}
}
}

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

@ -347,5 +347,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(expectedShort, actualShort);
}
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void RoundInplace(int seed)
{
Block8x8F s = CreateRandomFloatBlock(-500, 500, seed);
Block8x8F d = s;
d.RoundInplace();
this.Output.WriteLine(s.ToString());
this.Output.WriteLine(d.ToString());
for (int i = 0; i < 64; i++)
{
float expected = MathF.Round(s[i]);
float actual = d[i];
Assert.Equal(expected, actual);
}
}
}
}

30
tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs

@ -33,36 +33,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private ITestOutputHelper Output { get; }
private static int R(float f) => (int)MathF.Round(f, MidpointRounding.AwayFromZero);
// TODO: Move this to a proper test class!
[Theory]
[InlineData(0.32, 54.5, -3.5, -4.1)]
[InlineData(5.3, 536.4, 4.5, 8.1)]
public void Vector4_PseudoRound(float x, float y, float z, float w)
{
var v = new Vector4(x, y, z, w);
Vector4 actual = v.PseudoRound();
Assert.Equal(
R(v.X),
(int)actual.X
);
Assert.Equal(
R(v.Y),
(int)actual.Y
);
Assert.Equal(
R(v.Z),
(int)actual.Z
);
Assert.Equal(
R(v.W),
(int)actual.W
);
}
[Theory]
[MemberData(nameof(CommonConversionData))]
public void ConvertFromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed)

Loading…
Cancel
Save