From bbd4daffe5b301c8f11ba80ac07cbe334a39f3b6 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 2 Feb 2020 23:51:48 +0100 Subject: [PATCH] CopyTo WIP --- .../Allocators/ArrayPoolMemoryAllocator.cs | 12 +-- .../Memory/Allocators/MemoryAllocator.cs | 4 +- .../Allocators/SimpleGcMemoryAllocator.cs | 4 +- .../MemoryGroupExtensions.cs | 83 ++++++++++++++++++- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 2 +- .../DiscontiguousBuffers/MemoryGroupIndex.cs | 5 +- .../MemoryGroupIndexTests.cs | 5 +- .../MemoryGroupTests.Allocate.cs | 6 +- .../MemoryGroupTests.CopyTo.cs | 5 ++ .../DiscontiguousBuffers/MemoryGroupTests.cs | 26 ------ .../MemoryGroupTestsBase.cs | 32 +++++++ .../TestUtilities/TestMemoryAllocator.cs | 4 +- 12 files changed, 144 insertions(+), 44 deletions(-) create mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index d341e93af..883d57851 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -83,19 +83,19 @@ namespace SixLabors.ImageSharp.Memory /// public int PoolSelectorThresholdInBytes { get; } + /// + /// Gets or sets the length of the largest contiguous buffer that can be handled by this allocator instance. + /// + public int BufferCapacityInBytes { get; set; } = DefaultBufferCapacity; + /// public override void ReleaseRetainedResources() { this.InitArrayPools(); } - /// - /// Gets or sets the length of the largest contiguous buffer that can be handled by this allocator instance. - /// - public int MaximumContiguousBufferLength { get; set; } = Int32.MaxValue; - /// - protected internal override int GetBufferCapacity() => this.MaximumContiguousBufferLength; + protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 9ed322c9c..c6e92f23f 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -10,11 +10,13 @@ namespace SixLabors.ImageSharp.Memory /// public abstract class MemoryAllocator { + internal const int DefaultBufferCapacity = int.MaxValue / 2; + /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. /// /// The length of the largest contiguous buffer that can be handled by this allocator instance. - protected internal abstract int GetBufferCapacity(); + protected internal abstract int GetBufferCapacityInBytes(); /// /// Allocates an , holding a of length . diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 293e807ef..b417df351 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -7,12 +7,12 @@ using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory { /// - /// Implements by newing up arrays by the GC on every allocation requests. + /// Implements by newing up managed arrays on every allocation request. /// public sealed class SimpleGcMemoryAllocator : MemoryAllocator { /// - protected internal override int GetBufferCapacity() => int.MaxValue; + protected internal override int GetBufferCapacityInBytes() => DefaultBufferCapacity; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 3e0df15ea..68a1f2e80 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -10,7 +10,88 @@ namespace SixLabors.ImageSharp.Memory public static void CopyTo(this IMemoryGroup source, IMemoryGroup target) where T : struct { - throw new NotImplementedException(); + Guard.NotNull(source, nameof(source)); + Guard.NotNull(target, nameof(target)); + Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); + Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); + Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); + + if (source.IsEmpty()) + { + return; + } + + long position = 0; + var srcCur = new MemoryGroupCursor(source); + var trgCur = new MemoryGroupCursor(target); + + while (position < source.TotalLength) + { + int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); + Span srcSpan = srcCur.GetSpan(fwd); + Span trgSpan = trgCur.GetSpan(fwd); + srcSpan.CopyTo(trgSpan); + + srcCur.Forward(fwd); + trgCur.Forward(fwd); + position += fwd; + } + } + + public static bool IsEmpty(this IMemoryGroup group) + where T : struct + => group.Count == 0; + + private struct MemoryGroupCursor + where T : struct + { + private readonly IMemoryGroup memoryGroup; + + private int bufferIndex; + + private int elementIndex; + + public MemoryGroupCursor(IMemoryGroup memoryGroup) + { + this.memoryGroup = memoryGroup; + this.bufferIndex = 0; + this.elementIndex = 0; + } + + private bool IsAtLastBuffer => this.bufferIndex == this.memoryGroup.Count - 1; + + private int CurrentBufferLength => this.memoryGroup[this.bufferIndex].Length; + + public Span GetSpan(int length) + { + return this.memoryGroup[this.bufferIndex].Span.Slice(this.elementIndex, length); + } + + public int LookAhead() + { + return this.CurrentBufferLength - this.elementIndex; + } + + public void Forward(int steps) + { + int nextIdx = this.elementIndex + steps; + int currentBufferLength = this.CurrentBufferLength; + + if (nextIdx < currentBufferLength) + { + this.elementIndex = nextIdx; + } + else if (nextIdx == currentBufferLength) + { + this.bufferIndex++; + this.elementIndex = 0; + } + else + { + // If we get here, it indicates a bug in CopyTo: + throw new ArgumentException("Can't forward multiple buffers!", nameof(steps)); + } + } } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index ac43d6847..3883a111d 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); Guard.MustBeGreaterThan(blockAlignment, 0, nameof(blockAlignment)); - int blockCapacityInElements = allocator.GetBufferCapacity() / ElementSize; + int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize; if (blockAlignment > blockCapacityInElements) { throw new InvalidMemoryOperationException(); diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs index 710f90216..d619aec29 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs index 6a9d322f6..f0cc18f29 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs @@ -1,4 +1,7 @@ -using Xunit; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using Xunit; namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index 1a617d396..0c96f3d78 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers int expectedSizeOfLastBuffer) where T : struct { - this.MemoryAllocator.BufferCapacity = bufferCapacity; + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; // Act: using var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferAlignment); @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers [Fact] public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() { - this.MemoryAllocator.BufferCapacity = 84; // 42 * Int16 + this.MemoryAllocator.BufferCapacityInBytes = 84; // 42 * Int16 Assert.Throws(() => { @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers [InlineData(AllocationOptions.Clean)] public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) { - this.MemoryAllocator.BufferCapacity = 200; + this.MemoryAllocator.BufferCapacityInBytes = 200; HashSet bufferHashes; diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs index 206d6499b..fd679d071 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs @@ -10,15 +10,20 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { public class CopyTo : MemoryGroupTestsBase { +#pragma warning disable SA1509 public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = new TheoryData() { + { 20, 10, 20, 10 }, { 20, 5, 20, 4 }, { 20, 4, 20, 5 }, { 18, 6, 20, 5 }, { 19, 10, 20, 10 }, { 21, 10, 22, 2 }, { 1, 5, 5, 4 }, + + { 30, 12, 40, 5 }, + { 30, 5, 40, 12 }, }; [Theory] diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index b9fea3497..f7865b00c 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -26,7 +26,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers Assert.False(g.IsValid); } - [StructLayout(LayoutKind.Sequential, Size = 5)] private struct S5 { @@ -39,29 +38,4 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers public override string ToString() => "S4"; } } - - public abstract class MemoryGroupTestsBase - { - internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); - - internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false) - { - this.MemoryAllocator.BufferCapacity = bufferLength; - var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferLength); - - if (!fillSequence) - { - return g; - } - - int j = 1; - for (MemoryGroupIndex i = g.MinIndex(); i < g.MaxIndex(); i += 1) - { - g.SetElementAt(i, j); - j++; - } - - return g; - } - } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs new file mode 100644 index 000000000..acd24e343 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public abstract class MemoryGroupTestsBase + { + internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); + + internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false) + { + this.MemoryAllocator.BufferCapacityInBytes = bufferLength * sizeof(int); + var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferLength); + + if (!fillSequence) + { + return g; + } + + int j = 1; + for (MemoryGroupIndex i = g.MinIndex(); i < g.MaxIndex(); i += 1) + { + g.SetElementAt(i, j); + j++; + } + + return g; + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index a77e7f2f2..dd928cb75 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -26,13 +26,13 @@ namespace SixLabors.ImageSharp.Tests.Memory /// public byte DirtyValue { get; } - public int BufferCapacity { get; set; } = int.MaxValue; + public int BufferCapacityInBytes { get; set; } = int.MaxValue; public IReadOnlyList AllocationLog => this.allocationLog; public IReadOnlyList ReturnLog => this.returnLog; - protected internal override int GetBufferCapacity() => this.BufferCapacity; + protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) {