// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Memory { using System; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using Xunit; public class ArrayPoolMemoryManagerTests { private const int MaxPooledBufferSizeInBytes = 2048; private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2; private MemoryManager MemoryManager { get; set; } = new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); /// /// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location /// private bool CheckIsRentingPooledBuffer(int length) where T : struct { IBuffer buffer = this.MemoryManager.Allocate(length); ref T ptrToPrevPosition0 = ref buffer.DangerousGetPinnableReference(); buffer.Dispose(); buffer = this.MemoryManager.Allocate(length); bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.DangerousGetPinnableReference()); buffer.Dispose(); return sameBuffers; } public class BufferTests : BufferTestSuite { public BufferTests() : base(new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes)) { } } public class Constructor { [Fact] public void WhenBothParametersPassedByUser() { var mgr = new ArrayPoolMemoryManager(1111, 666); Assert.Equal(1111, mgr.MaxPoolSizeInBytes); Assert.Equal(666, mgr.PoolSelectorThresholdInBytes); } [Fact] public void WhenPassedOnly_MaxPooledBufferSizeInBytes_SmallerThresholdValueIsAutoCalculated() { var mgr = new ArrayPoolMemoryManager(5000); Assert.Equal(5000, mgr.MaxPoolSizeInBytes); Assert.True(mgr.PoolSelectorThresholdInBytes < mgr.MaxPoolSizeInBytes); } [Fact] public void When_PoolSelectorThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_ExceptionIsThrown() { Assert.ThrowsAny(() => { new ArrayPoolMemoryManager(100, 200); }); } } [StructLayout(LayoutKind.Explicit, Size = MaxPooledBufferSizeInBytes / 5)] struct LargeStruct { } [Theory] [InlineData(32)] [InlineData(512)] [InlineData(MaxPooledBufferSizeInBytes - 1)] public void SmallBuffersArePooled_OfByte(int size) { Assert.True(this.CheckIsRentingPooledBuffer(size)); } [Theory] [InlineData(128 * 1024 * 1024)] [InlineData(MaxPooledBufferSizeInBytes + 1)] public void LargeBuffersAreNotPooled_OfByte(int size) { Assert.False(this.CheckIsRentingPooledBuffer(size)); } [Fact] public unsafe void SmallBuffersArePooled_OfBigValueType() { int count = MaxPooledBufferSizeInBytes / sizeof(LargeStruct) - 1; Assert.True(this.CheckIsRentingPooledBuffer(count)); } [Fact] public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() { int count = MaxPooledBufferSizeInBytes / sizeof(LargeStruct) + 1; Assert.False(this.CheckIsRentingPooledBuffer(count)); } [Theory] [InlineData(false)] [InlineData(true)] public void CleaningRequests_AreControlledByAllocationParameter_Clean(bool clean) { using (IBuffer firstAlloc = this.MemoryManager.Allocate(42)) { firstAlloc.Span.Fill(666); } using (IBuffer secondAlloc = this.MemoryManager.Allocate(42, clean)) { int expected = clean ? 0 : 666; Assert.Equal(expected, secondAlloc.Span[0]); } } [Theory] [InlineData(false)] [InlineData(true)] public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive) { IBuffer buffer = this.MemoryManager.Allocate(32); ref int ptrToPrev0 = ref MemoryMarshal.GetReference(buffer.Span); if (!keepBufferAlive) { buffer.Dispose(); } this.MemoryManager.ReleaseRetainedResources(); buffer = this.MemoryManager.Allocate(32); Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.DangerousGetPinnableReference())); } [Fact] public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed() { IBuffer buffer = this.MemoryManager.Allocate(32); this.MemoryManager.ReleaseRetainedResources(); buffer.Dispose(); } [Fact] public void AllocationOverLargeArrayThreshold_UsesDifferentPool() { int arrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); IBuffer small = this.MemoryManager.Allocate(arrayLengthThreshold - 1); ref int ptr2Small = ref small.DangerousGetPinnableReference(); small.Dispose(); IBuffer large = this.MemoryManager.Allocate(arrayLengthThreshold + 1); Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.DangerousGetPinnableReference())); } [Fact] public void CreateWithAggressivePooling() { this.MemoryManager = ArrayPoolMemoryManager.CreateWithAggressivePooling(); Assert.True(this.CheckIsRentingPooledBuffer(4096 * 4096)); } [Fact] public void CreateDefault() { this.MemoryManager = ArrayPoolMemoryManager.CreateDefault(); Assert.False(this.CheckIsRentingPooledBuffer(2 * 4096 * 4096)); Assert.True(this.CheckIsRentingPooledBuffer(2048 * 2048)); } [Fact] public void CreateWithModeratePooling() { this.MemoryManager = ArrayPoolMemoryManager.CreateWithModeratePooling(); Assert.False(this.CheckIsRentingPooledBuffer(2048 * 2048)); Assert.True(this.CheckIsRentingPooledBuffer(1024 * 16)); } } }