From 3064b2ba20fcc6ecddd112d210acb233d7bf13c4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 8 Mar 2017 14:11:23 +0100 Subject: [PATCH] better BufferPointer.Copy() --- src/ImageSharp/Colors/Color.BulkOperations.cs | 1 + src/ImageSharp/Common/Memory/BufferPointer.cs | 106 ++++++------------ .../Common/BufferPointerTests.cs | 102 ++++++++++++++++- 3 files changed, 133 insertions(+), 76 deletions(-) diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index cdbfdfcd7..617114dff 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -66,6 +66,7 @@ namespace ImageSharp for (; src < srcEnd; src++, dst++) { + // TODO: We can benefit a lot of future Vector API-s here (https://github.com/dotnet/corefx/issues/15957) dst->Load(*src); } diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferPointer.cs index e600f4188..b470b4db5 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer.cs @@ -5,6 +5,7 @@ namespace ImageSharp { + using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -17,7 +18,7 @@ namespace ImageSharp /// /// It's worth to use Marshal.Copy() over this size. /// - private const uint ByteCountThreshold = 1024u; + private const int ByteCountThreshold = 1024; /// /// Copy 'count' number of elements of the same type from 'source' to 'dest' @@ -27,20 +28,10 @@ namespace ImageSharp /// The destination . /// The number of elements to copy [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(BufferPointer source, BufferPointer destination, int count) + public static void Copy(BufferPointer source, BufferPointer destination, int count) where T : struct { - uint byteCount = USizeOf(count); - - if (byteCount > ByteCountThreshold) - { - if (TryMarshalCopy(source, destination, count)) - { - return; - } - } - - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + CopyImpl(source, destination, count); } /// @@ -51,20 +42,10 @@ namespace ImageSharp /// The destination buffer. /// The number of elements to copy from 'source' [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInSource) + public static void Copy(BufferPointer source, BufferPointer destination, int countInSource) where T : struct { - uint byteCount = USizeOf(countInSource); - - if (byteCount > ByteCountThreshold) - { - if (TryMarshalCopy(source, destination, countInSource)) - { - return; - } - } - - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + CopyImpl(source, destination, countInSource); } /// @@ -112,60 +93,37 @@ namespace ImageSharp => (uint)SizeOf(count); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryMarshalCopy(BufferPointer source, BufferPointer destination, int count) - where TSource : struct + private static unsafe void CopyImpl(BufferPointer source, BufferPointer destination, int count) + where T : struct where TDest : struct { - // Pattern Based On: - // https://github.com/dotnet/corefx/blob/master/src/System.Numerics.Vectors/src/System/Numerics/Vector.cs#L12 - // - // Note: The following patterns are used throughout the code here and are described here - // - // PATTERN: - // if (typeof(T) == typeof(Int32)) { ... } - // else if (typeof(T) == typeof(Single)) { ... } - // EXPLANATION: - // At runtime, each instantiation of BufferPointer will be type-specific, and each of these typeof blocks will be eliminated, - // as typeof(T) is a (JIT) compile-time constant for each instantiation. This design was chosen to eliminate any overhead from - // delegates and other patterns. - if (typeof(TSource) == typeof(long)) - { - long[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; - } - else if (typeof(TSource) == typeof(int)) - { - int[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; - } - else if (typeof(TSource) == typeof(uint)) - { - int[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; - } - else if (typeof(TSource) == typeof(short)) - { - short[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; - } - else if (typeof(TSource) == typeof(ushort)) - { - short[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; - } - else if (typeof(TSource) == typeof(byte)) + int byteCount = SizeOf(count); + + if (byteCount > ByteCountThreshold) { - byte[] srcArray = Unsafe.As(source.Array); - Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); - return true; + if (Unsafe.SizeOf() == sizeof(long)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } + else if (Unsafe.SizeOf() == sizeof(byte)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } } - return false; + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs index fc26bf097..58fe2a199 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs @@ -32,6 +32,37 @@ namespace ImageSharp.Tests.Common } } + /// + /// sizeof(AlignedFoo) == sizeof(long) + /// + public struct AlignedFoo + { + public int A; + + public int B; + + static AlignedFoo() + { + Assert.Equal(sizeof(AlignedFoo), sizeof(long)); + } + + public AlignedFoo(int a, int b) + { + this.A = a; + this.B = b; + } + + internal static AlignedFoo[] CreateArray(int size) + { + AlignedFoo[] result = new AlignedFoo[size]; + for (int i = 0; i < size; i++) + { + result[i] = new AlignedFoo(i + 1, i + 1); + } + return result; + } + } + [Fact] public void AsBytes() { @@ -108,7 +139,6 @@ namespace ImageSharp.Tests.Common Assert.NotEqual(default(T), data[idx]); } - private static byte[] CreateTestBytes(int count) { byte[] result = new byte[count]; @@ -156,6 +186,33 @@ namespace ImageSharp.Tests.Common Assert.NotEqual(source[count], dest[count]); } + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void GenericToOwnType_Aligned(int count) + { + AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); + AlignedFoo[] dest = new AlignedFoo[count + 5]; + + fixed (AlignedFoo* pSource = source) + fixed (AlignedFoo* pDest = dest) + { + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(dest, pDest, 1); + + BufferPointer.Copy(apSource, apDest, count - 1); + } + + AssertNotDefault(source, 1); + AssertNotDefault(dest, 1); + + Assert.NotEqual(source[0], dest[0]); + Assert.Equal(source[1], dest[1]); + Assert.Equal(source[2], dest[2]); + Assert.Equal(source[count - 1], dest[count - 1]); + Assert.NotEqual(source[count], dest[count]); + } + [Theory] [InlineData(4)] [InlineData(1500)] @@ -209,7 +266,34 @@ namespace ImageSharp.Tests.Common Assert.True(ElementsAreEqual(source, dest, count - 1)); Assert.False(ElementsAreEqual(source, dest, count)); } - + + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void GenericToBytes_Aligned(int count) + { + int destCount = count * sizeof(Foo); + AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); + byte[] dest = new byte[destCount + sizeof(AlignedFoo) * 2]; + + fixed (AlignedFoo* pSource = source) + fixed (byte* pDest = dest) + { + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(dest, pDest, sizeof(AlignedFoo)); + + BufferPointer.Copy(apSource, apDest, count - 1); + } + + AssertNotDefault(source, 1); + + Assert.False(ElementsAreEqual(source, dest, 0)); + Assert.True(ElementsAreEqual(source, dest, 1)); + Assert.True(ElementsAreEqual(source, dest, 2)); + Assert.True(ElementsAreEqual(source, dest, count - 1)); + Assert.False(ElementsAreEqual(source, dest, count)); + } + [Theory] [InlineData(4)] [InlineData(1500)] @@ -296,6 +380,20 @@ namespace ImageSharp.Tests.Common } } + internal static bool ElementsAreEqual(AlignedFoo[] array, byte[] rawArray, int index) + { + fixed (AlignedFoo* pArray = array) + fixed (byte* pRaw = rawArray) + { + AlignedFoo* pCasted = (AlignedFoo*)pRaw; + + AlignedFoo val1 = pArray[index]; + AlignedFoo val2 = pCasted[index]; + + return val1.Equals(val2); + } + } + internal static bool ElementsAreEqual(int[] array, byte[] rawArray, int index) { fixed (int* pArray = array)