namespace ImageSharp.Benchmarks.General { using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; // Pixel indexing benchmarks compare different methods for getting/setting all pixel values in a subsegment of a single pixel row. 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 Vector4[] array; private int width; public Data(PinnedImageBuffer buffer) { this.pointer = (Vector4*)buffer.Pointer; this.pinnable = Unsafe.As>(buffer.Array); this.array = 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 IndexWithReferencesOnPinnableIncorrectImpl(int x, int y, Vector4 v) { int elementOffset = (y * this.width) + x; // Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68 Unsafe.Add(ref this.pinnable.Data, elementOffset) = v; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref Vector4 IndexWithReferencesOnPinnableIncorrectRefReturnImpl(int x, int y) { int elementOffset = (y * this.width) + x; // Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68 return ref Unsafe.Add(ref this.pinnable.Data, elementOffset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IndexWithReferencesOnArrayImpl(int x, int y, Vector4 v) { int elementOffset = (y * this.width) + x; Unsafe.Add(ref this.array[0], elementOffset) = v; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref Vector4 IndexWithReferencesOnArrayRefReturnImpl(int x, int y) { int elementOffset = (y * this.width) + x; return ref Unsafe.Add(ref this.array[0], elementOffset); } } internal PinnedImageBuffer buffer; protected int width; protected int startIndex; protected int endIndex; protected Vector4* pointer; protected Vector4[] array; protected Pinnable pinnable; // [Params(1024)] public int Count { get; set; } = 1024; [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 class PixelIndexingGetter : PixelIndexing { [Benchmark(Description = "Index.Get: Pointers+arithmetics", Baseline = true)] public Vector4 IndexWithPointersBasic() { Vector4 sum = Vector4.Zero; Data data = new Data(this.buffer); int y = this.startIndex; for (int x = this.startIndex; x < this.endIndex; x++) { sum += data.GetPointersBasicImpl(x, y); } return sum; } [Benchmark(Description = "Index.Get: Pointers+SRCS.Unsafe")] public Vector4 IndexWithPointersSrcsUnsafe() { Vector4 sum = Vector4.Zero; Data data = new Data(this.buffer); int y = this.startIndex; for (int x = this.startIndex; x < this.endIndex; x++) { sum += data.GetPointersSrcsUnsafeImpl(x, y); } return sum; } [Benchmark(Description = "Index.Get: References")] public Vector4 IndexWithReferences() { Vector4 sum = Vector4.Zero; Data data = new Data(this.buffer); int y = this.startIndex; for (int x = this.startIndex; x < this.endIndex; x++) { sum += data.GetReferencesImpl(x, y); } return sum; } [Benchmark(Description = "Index.Get: References|refreturns")] public Vector4 IndexWithReferencesRefReturns() { Vector4 sum = Vector4.Zero; Data data = new Data(this.buffer); int y = this.startIndex; for (int x = this.startIndex; x < this.endIndex; x++) { sum += data.GetReferencesRefReturnsImpl(x, y); } return sum; } } public 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); int y = this.startIndex; for (int x = this.startIndex; x < this.endIndex; x++) { data.IndexWithPointersBasicImpl(x, y, 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); int y = this.startIndex; for (int x = this.startIndex; x < this.endIndex; x++) { data.IndexWithPointersSrcsUnsafeImpl(x, y, v); } } [Benchmark(Description = "Index.Set: References|IncorrectPinnable")] public void IndexWithReferencesPinnableBasic() { Vector4 v = new Vector4(1, 2, 3, 4); Data data = new Data(this.buffer); int y = this.startIndex; for (int x = this.startIndex; x < this.endIndex; x++) { data.IndexWithReferencesOnPinnableIncorrectImpl(x, y, v); } } [Benchmark(Description = "Index.Set: References|IncorrectPinnable|refreturn")] public void IndexWithReferencesPinnableRefReturn() { Vector4 v = new Vector4(1, 2, 3, 4); Data data = new Data(this.buffer); int y = this.startIndex; for (int x = this.startIndex; x < this.endIndex; x++) { data.IndexWithReferencesOnPinnableIncorrectRefReturnImpl(x, y) = v; } } [Benchmark(Description = "Index.Set: References|Array[0]")] public void IndexWithReferencesArrayBasic() { Vector4 v = new Vector4(1, 2, 3, 4); Data data = new Data(this.buffer); int y = this.startIndex; for (int x = this.startIndex; x < this.endIndex; x++) { data.IndexWithReferencesOnArrayImpl(x, y, v); } } [Benchmark(Description = "Index.Set: References|Array[0]|refreturn")] public void IndexWithReferencesArrayRefReturn() { Vector4 v = new Vector4(1, 2, 3, 4); Data data = new Data(this.buffer); int y = this.startIndex; for (int x = this.startIndex; x < this.endIndex; x++) { data.IndexWithReferencesOnArrayRefReturnImpl(x, y) = v; } } [Benchmark(Description = "Index.Set: SmartUnsafe")] public void SmartUnsafe() { Vector4 v = new Vector4(1, 2, 3, 4); Data data = new Data(this.buffer); // This method is basically an unsafe variant of .GetRowSpan(y) + indexing individual pixels in the row. // If a user seriously needs by-pixel manipulation to be performant, we should provide this option. ref Vector4 rowStart = ref data.IndexWithReferencesOnArrayRefReturnImpl(this.startIndex, this.startIndex); for (int i = 0; i < this.Count; i++) { // We don't have to add 'Width * y' here! Unsafe.Add(ref rowStart, i) = v; } } } }