From c756c3a274d97b37b47002c17ed80d897f90cfbe Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 2 Feb 2020 19:23:30 +0100 Subject: [PATCH] test cases for MemoryGroup.Allocate() --- src/ImageSharp/ImageSharp.csproj.DotSettings | 2 + .../Allocators/ArrayPoolMemoryAllocator.cs | 2 +- .../Memory/Allocators/MemoryAllocator.cs | 5 +- .../Allocators/SimpleGcMemoryAllocator.cs | 2 +- .../DiscontiguousBuffers/IMemoryGroup{T}.cs | 21 ++++ .../InvalidMemoryOperationException.cs | 2 +- .../MemoryGroupView{T}.cs} | 18 ++-- .../MemoryGroup{T}.Consumed.cs} | 6 +- .../MemoryGroup{T}.Owned.cs} | 8 +- .../MemoryGroup{T}.cs} | 16 ++-- .../IUniformMemoryGroup{T}.cs | 15 --- .../DiscontiguousBuffers/MemoryGroupTests.cs | 96 +++++++++++++++++++ .../TestUtilities/TestMemoryAllocator.cs | 4 +- 13 files changed, 154 insertions(+), 43 deletions(-) create mode 100644 src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs rename src/ImageSharp/Memory/{DiscontinuousProto => DiscontiguousBuffers}/InvalidMemoryOperationException.cs (65%) rename src/ImageSharp/Memory/{DiscontinuousProto/UniformMemoryGroupView{T}.cs => DiscontiguousBuffers/MemoryGroupView{T}.cs} (77%) rename src/ImageSharp/Memory/{DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs => DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs} (84%) rename src/ImageSharp/Memory/{DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs => DiscontiguousBuffers/MemoryGroup{T}.Owned.cs} (86%) rename src/ImageSharp/Memory/{DiscontinuousProto/UniformMemoryGroup{T}.cs => DiscontiguousBuffers/MemoryGroup{T}.cs} (65%) delete mode 100644 src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs create mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs diff --git a/src/ImageSharp/ImageSharp.csproj.DotSettings b/src/ImageSharp/ImageSharp.csproj.DotSettings index 018ca75cd..6896e069c 100644 --- a/src/ImageSharp/ImageSharp.csproj.DotSettings +++ b/src/ImageSharp/ImageSharp.csproj.DotSettings @@ -2,6 +2,8 @@ True True True + True + True True True True diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index 57a5b77bc..e53929c5c 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Memory public int MaximumContiguousBufferLength { get; set; } = Int32.MaxValue; /// - protected internal override int GetMaximumContiguousBufferLength() => this.MaximumContiguousBufferLength; + protected internal override int GetBlockCapacity() => this.MaximumContiguousBufferLength; /// 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 1e1f69784..ccb5bf2e8 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -11,9 +11,10 @@ namespace SixLabors.ImageSharp.Memory public abstract class MemoryAllocator { /// - /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance. + /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. /// - protected internal abstract int GetMaximumContiguousBufferLength(); + /// The length of the largest contiguous buffer that can be handled by this allocator instance. + protected internal abstract int GetBlockCapacity(); /// /// Allocates an , holding a of length . diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index ee9afbac5..88830c551 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Memory public sealed class SimpleGcMemoryAllocator : MemoryAllocator { /// - protected internal override int GetMaximumContiguousBufferLength() => int.MaxValue; + protected internal override int GetBlockCapacity() => int.MaxValue; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs new file mode 100644 index 000000000..eaacef713 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents discontiguous group of multiple uniformly-sized memory segments. + /// The last segment can be smaller than the preceding ones. + /// + /// The element type. + public interface IMemoryGroup : IReadOnlyList> + where T : struct + { + /// + /// Gets the number of elements per contiguous sub-block. + /// + public int BlockSize { get; } + + bool IsValid { get; } + } +} diff --git a/src/ImageSharp/Memory/DiscontinuousProto/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs similarity index 65% rename from src/ImageSharp/Memory/DiscontinuousProto/InvalidMemoryOperationException.cs rename to src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs index f756a1246..b211a13f3 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/InvalidMemoryOperationException.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs @@ -1,6 +1,6 @@ using System; -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +namespace SixLabors.ImageSharp.Memory { public class InvalidMemoryOperationException : InvalidOperationException { diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs similarity index 77% rename from src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs rename to src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs index 4e5b04dfd..a2d5c0579 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs @@ -3,24 +3,24 @@ using System.Buffers; using System.Collections; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +namespace SixLabors.ImageSharp.Memory { /// - /// Implements , defining a view for + /// Implements , defining a view for /// rather than owning the segments. /// /// /// This type provides an indirection, protecting the users of publicly exposed memory API-s - /// from internal memory-swaps. Whenever an internal swap happens, the + /// from internal memory-swaps. Whenever an internal swap happens, the /// instance becomes invalid, throwing an exception on all operations. /// /// The element type. - internal class UniformMemoryGroupView : IUniformMemoryGroup where T : struct + internal class MemoryGroupView : IMemoryGroup where T : struct { - private readonly UniformMemoryGroup owner; + private readonly Memory.MemoryGroup owner; private readonly MemoryOwnerWrapper[] memoryWrappers; - public UniformMemoryGroupView(UniformMemoryGroup owner) + public MemoryGroupView(Memory.MemoryGroup owner) { this.IsValid = true; this.owner = owner; @@ -40,15 +40,17 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto public Memory this[int index] => throw new NotImplementedException(); + public int BlockSize => this.owner.BlockSize; + public bool IsValid { get; internal set; } class MemoryOwnerWrapper : MemoryManager { - private UniformMemoryGroupView view; + private MemoryGroupView view; private int index; - public MemoryOwnerWrapper(UniformMemoryGroupView view, int index) + public MemoryOwnerWrapper(MemoryGroupView view, int index) { this.view = view; this.index = index; diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs similarity index 84% rename from src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs rename to src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 17410e900..03f61b75b 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +namespace SixLabors.ImageSharp.Memory { - internal abstract partial class UniformMemoryGroup + internal abstract partial class MemoryGroup { // Analogous to the "consumed" variant of MemorySource - private class Consumed : UniformMemoryGroup + private class Consumed : MemoryGroup { private readonly ReadOnlyMemory> source; diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs similarity index 86% rename from src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs rename to src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index d02975cbc..b15d7d676 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -3,12 +3,12 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +namespace SixLabors.ImageSharp.Memory { // Analogous to the "owned" variant of MemorySource - internal abstract partial class UniformMemoryGroup + internal abstract partial class MemoryGroup { - private class Owned : UniformMemoryGroup + private class Owned : MemoryGroup { private IMemoryOwner[] memoryOwners; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto { if (this.memoryOwners == null) { - throw new ObjectDisposedException(nameof(UniformMemoryGroup)); + throw new ObjectDisposedException(nameof(MemoryGroup)); } } } diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs similarity index 65% rename from src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs rename to src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 794239377..670a0aaf6 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -2,7 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +namespace SixLabors.ImageSharp.Memory { /// /// Represents discontinuous group of multiple uniformly-sized memory segments. @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto /// and . /// /// The element type. - internal abstract partial class UniformMemoryGroup : IUniformMemoryGroup, IDisposable where T : struct + internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable where T : struct { public abstract IEnumerator> GetEnumerator(); @@ -22,26 +22,28 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto public abstract void Dispose(); + public int BlockSize { get; } + public bool IsValid { get; protected set; } // bufferLengthAlignment == image.Width in row-major images - public static UniformMemoryGroup Allocate(MemoryAllocator allocator, long length, int bufferLengthAlignment) + public static MemoryGroup Allocate(MemoryAllocator allocator, long totalLength, int blockAlignment) { - long bufferCount = length / allocator.GetMaximumContiguousBufferLength(); + long bufferCount = totalLength / allocator.GetBlockCapacity(); // TODO: Adjust bufferCount, and calculate the uniform buffer length with respect to bufferLengthAlignment, and allocate bufferCount buffers throw new NotImplementedException(); } - public static UniformMemoryGroup Wrap(params Memory[] source) => Wrap(source.AsMemory()); + public static MemoryGroup Wrap(params Memory[] source) => Wrap(source.AsMemory()); - public static UniformMemoryGroup Wrap(ReadOnlyMemory> source) + public static MemoryGroup Wrap(ReadOnlyMemory> source) { return new Consumed(source); } // Analogous to current MemorySource.SwapOrCopyContent() - public static void SwapOrCopyContent(UniformMemoryGroup destination, UniformMemoryGroup source) + public static void SwapOrCopyContent(MemoryGroup destination, MemoryGroup source) { throw new NotImplementedException(); } diff --git a/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs deleted file mode 100644 index d0ec69bea..000000000 --- a/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto -{ - /// - /// Represents discontinuous group of multiple uniformly-sized memory segments. - /// The last segment can be smaller than the preceding ones. - /// - /// The element type. - public interface IUniformMemoryGroup : IReadOnlyList> where T : struct - { - bool IsValid { get; } - } -} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs new file mode 100644 index 000000000..adb398ee6 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -0,0 +1,96 @@ +using System.Linq; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public class MemoryGroupTests + { + public class Allocate + { + private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); + +#pragma warning disable SA1509 + public static TheoryData AllocateData = + new TheoryData() + { + { default(S5), 22, 4, 4, 1, 4, 4 }, + { default(S5), 22, 4, 7, 2, 4, 3 }, + { default(S5), 22, 4, 8, 2, 4, 4 }, + { default(S5), 22, 4, 21, 5, 4, 1 }, + { default(S5), 22, 4, 0, 0, -1, -1 }, + + { default(S4), 50, 12, 12, 1, 12, 12 }, + { default(S4), 50, 7, 12, 2, 7, 5 }, + { default(S4), 50, 6, 12, 2, 6, 6 }, + { default(S4), 50, 5, 12, 2, 10, 2 }, + { default(S4), 50, 4, 12, 1, 12, 12 }, + { default(S4), 50, 3, 12, 1, 12, 12 }, + { default(S4), 50, 2, 12, 1, 12, 12 }, + { default(S4), 50, 1, 12, 1, 12, 12 }, + + { default(S4), 50, 12, 13, 2, 12, 1 }, + { default(S4), 50, 7, 21, 3, 7, 7 }, + { default(S4), 50, 7, 23, 3, 7, 2 }, + + { default(byte), 1000, 512, 2047, 4, 512, 511 } + }; + + [Theory] + [MemberData(nameof(AllocateData))] + public void CreatesBlocksOfCorrectSizes( + T dummy, + int blockCapacity, + int blockAlignment, + long totalLength, + int expectedNumberOfBlocks, + int expectedBlockSize, + int expectedSizeOfLastBlock) + where T : struct + { + this.memoryAllocator.BlockCapacity = blockCapacity; + + // Act: + using var g = MemoryGroup.Allocate(this.memoryAllocator, totalLength, blockAlignment); + + // Assert: + Assert.Equal(expectedNumberOfBlocks, g.Count); + Assert.Equal(expectedBlockSize, g.BlockSize); + if (g.Count == 0) + { + return; + } + + for (int i = 0; i < g.Count - 1; i++) + { + Assert.Equal(g[i].Length, expectedBlockSize); + } + + Assert.Equal(g.Last().Length, expectedSizeOfLastBlock); + } + + [Fact] + public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() + { + this.memoryAllocator.BlockCapacity = 42; + + Assert.Throws(() => + { + MemoryGroup.Allocate(this.memoryAllocator, 50, 43); + }); + } + } + + + [StructLayout(LayoutKind.Sequential, Size = 5)] + private struct S5 + { + } + + [StructLayout(LayoutKind.Sequential, Size = 4)] + private struct S4 + { + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index b6b297fab..6f9473116 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -25,9 +25,11 @@ namespace SixLabors.ImageSharp.Tests.Memory /// public byte DirtyValue { get; } + public int BlockCapacity { get; set; } = int.MaxValue; + public IList AllocationLog => this.allocationLog; - protected internal override int GetMaximumContiguousBufferLength() => int.MaxValue; + protected internal override int GetBlockCapacity() => this.BlockCapacity; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) {