diff --git a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs index 8ef88814cd..5efa7bc07a 100644 --- a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs @@ -158,6 +158,14 @@ namespace ImageSharp return result; } + /// + /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element + /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. + /// + /// The reference to the 0th element + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T DangerousGetPinnableReference() => ref Unsafe.AsRef((void*)this.PointerAtOffset); + /// /// Forms a slice out of the given BufferSpan, beginning at 'start'. /// diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs new file mode 100644 index 0000000000..e912ea29f6 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs @@ -0,0 +1,74 @@ +namespace ImageSharp.Benchmarks +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + using BenchmarkDotNet.Attributes; + + using ImageSharp; + + /// + /// Compares two implementation candidates for general BulkPixelOperations.ToVector4(): + /// - One iterating with pointers + /// - One iterating with ref locals + /// + public unsafe class PackFromVector4ReferenceVsPointer + { + private PinnedBuffer destination; + + private PinnedBuffer source; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.destination = new PinnedBuffer(this.Count); + this.source = new PinnedBuffer(this.Count * 4); + } + + [Cleanup] + public void Cleanup() + { + this.source.Dispose(); + this.destination.Dispose(); + } + + [Benchmark(Baseline = true)] + public void PackUsingPointers() + { + Vector4* sp = (Vector4*)this.source.Pointer; + byte* dp = (byte*)this.destination.Pointer; + int count = this.Count; + int size = sizeof(ImageSharp.Color); + + for (int i = 0; i < count; i++) + { + Vector4 v = Unsafe.Read(sp); + ImageSharp.Color c = default(ImageSharp.Color); + c.PackFromVector4(v); + Unsafe.Write(dp, c); + + sp++; + dp += size; + } + } + + [Benchmark] + public void PackUsingReferences() + { + ref Vector4 sp = ref this.source.Array[0]; + ref ImageSharp.Color dp = ref this.destination.Array[0]; + int count = this.Count; + + for (int i = 0; i < count; i++) + { + dp.PackFromVector4(sp); + + sp = Unsafe.Add(ref sp, 1); + dp = Unsafe.Add(ref dp, 1); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs b/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs new file mode 100644 index 0000000000..8dc8744eb2 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs @@ -0,0 +1,238 @@ +namespace ImageSharp.Benchmarks.General +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + using BenchmarkDotNet.Attributes; + + public abstract unsafe class PixelIndexing + { + /// + /// https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Pinnable.cs + /// + protected class Pinnable + { + public T Data; + } + + /// + /// The indexer methods are encapsulated into a struct to make sure everything is inlined. + /// + internal struct Data + { + private Vector4* pointer; + + private Pinnable pinnable; + + private int width; + + public Data(PinnedImageBuffer buffer) + { + this.pointer = (Vector4*)buffer.Pointer; + this.pinnable = Unsafe.As>(buffer.Array); + this.width = buffer.Width; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 GetPointersBasicImpl(int x, int y) + { + return this.pointer[y * this.width + x]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 GetPointersSrcsUnsafeImpl(int x, int y) + { + return Unsafe.Read((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf())); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 GetReferencesImpl(int x, int y) + { + int elementOffset = (y * this.width) + x; + return Unsafe.Add(ref this.pinnable.Data, elementOffset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref Vector4 GetReferencesRefReturnsImpl(int x, int y) + { + int elementOffset = (y * this.width) + x; + return ref Unsafe.Add(ref this.pinnable.Data, elementOffset); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void IndexWithPointersBasicImpl(int x, int y, Vector4 v) + { + this.pointer[y * this.width + x] = v; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void IndexWithPointersSrcsUnsafeImpl(int x, int y, Vector4 v) + { + Unsafe.Write((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf()), v); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void IndexWithReferencesImpl(int x, int y, Vector4 v) + { + int elementOffset = (y * this.width) + x; + Unsafe.Add(ref this.pinnable.Data, elementOffset) = v; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref Vector4 IndexWithReferencesRefReturnImpl(int x, int y) + { + int elementOffset = (y * this.width) + x; + return ref Unsafe.Add(ref this.pinnable.Data, elementOffset); + } + } + + internal PinnedImageBuffer buffer; + + protected int width; + + protected int startIndex; + + protected int endIndex; + + protected Vector4* pointer; + + protected Vector4[] array; + + protected Pinnable pinnable; + + [Params(64, 256, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.width = 2048; + this.buffer = new PinnedImageBuffer(2048, 2048); + this.pointer = (Vector4*)this.buffer.Pointer; + this.array = this.buffer.Array; + this.pinnable = Unsafe.As>(this.array); + + this.startIndex = 2048 / 2 - (this.Count / 2); + this.endIndex = 2048 / 2 + (this.Count / 2); + } + + [Cleanup] + public void Cleanup() + { + this.buffer.Dispose(); + } + + } + + public unsafe class PixelIndexingGetter : PixelIndexing + { + [Benchmark(Description = "Index.Get: Pointers+arithmetics", Baseline = true)] + public Vector4 IndexWithPointersBasic() + { + Vector4 sum = Vector4.Zero; + Data data = new Data(this.buffer); + + for (int i = this.startIndex; i < this.endIndex; i++) + { + sum += data.GetPointersBasicImpl(i, i); + } + + return sum; + } + + [Benchmark(Description = "Index.Get: Pointers+SRCS.Unsafe")] + public Vector4 IndexWithPointersSrcsUnsafe() + { + Vector4 sum = Vector4.Zero; + Data data = new Data(this.buffer); + + for (int i = this.startIndex; i < this.endIndex; i++) + { + sum += data.GetPointersSrcsUnsafeImpl(i, i); + } + + return sum; + } + + [Benchmark(Description = "Index.Get: References")] + public Vector4 IndexWithReferences() + { + Vector4 sum = Vector4.Zero; + Data data = new Data(this.buffer); + + for (int i = this.startIndex; i < this.endIndex; i++) + { + sum += data.GetReferencesImpl(i, i); + } + + return sum; + } + + [Benchmark(Description = "Index.Get: References+refreturns")] + public Vector4 IndexWithReferencesRefReturns() + { + Vector4 sum = Vector4.Zero; + Data data = new Data(this.buffer); + + for (int i = this.startIndex; i < this.endIndex; i++) + { + sum += data.GetReferencesRefReturnsImpl(i, i); + } + + return sum; + } + } + + public unsafe class PixelIndexingSetter : PixelIndexing + { + [Benchmark(Description = "Index.Set: Pointers+arithmetics", Baseline = true)] + public void IndexWithPointersBasic() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + for (int i = this.startIndex; i < this.endIndex; i++) + { + data.IndexWithPointersBasicImpl(i, i, v); + } + } + + [Benchmark(Description = "Index.Set: Pointers+SRCS.Unsafe")] + public void IndexWithPointersSrcsUnsafe() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + for (int i = this.startIndex; i < this.endIndex; i++) + { + data.IndexWithPointersSrcsUnsafeImpl(i, i, v); + } + } + + [Benchmark(Description = "Index.Set: References")] + public void IndexWithReferencesBasic() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + for (int i = this.startIndex; i < this.endIndex; i++) + { + data.IndexWithReferencesImpl(i, i, v); + } + } + + [Benchmark(Description = "Index.Set: References+refreturn")] + public void IndexWithReferencesRefReturn() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + for (int i = this.startIndex; i < this.endIndex; i++) + { + data.IndexWithReferencesRefReturnImpl(i, i) = v; + } + } + + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs index aee032accd..32ec5dec62 100644 --- a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs @@ -201,6 +201,21 @@ namespace ImageSharp.Tests.Common } } + [Theory] + [InlineData(0, 4)] + [InlineData(2, 4)] + [InlineData(3, 4)] + public void DangerousGetPinnableReference(int start, int length) + { + Foo[] a = Foo.CreateArray(length); + fixed (Foo* p = a) + { + BufferSpan span = new BufferSpan(a, p, start); + ref Foo r = ref span.DangerousGetPinnableReference(); + + Assert.True(Unsafe.AreSame(ref a[start], ref r)); + } + } public class Copy {