зеркало из https://github.com/SixLabors/Core.git
Merge pull request #14 from SixLabors/af/public-memory-api
Publish SixLabors.Memory
This commit is contained in:
Коммит
fa868db8a4
|
@ -2,4 +2,5 @@
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1200/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1200/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1201/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1201/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1401/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1401/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1600/@EntryIndexedValue">DO_NOT_SHOW</s:String></wpf:ResourceDictionary>
|
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1600/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String></wpf:ResourceDictionary>
|
|
@ -19,8 +19,10 @@ namespace SixLabors
|
||||||
/// <param name="target">The target object, which cannot be null.</param>
|
/// <param name="target">The target object, which cannot be null.</param>
|
||||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null</exception>
|
||||||
|
/// <typeparam name="T">The type of the object to verify</typeparam>
|
||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
public static void NotNull(object target, string parameterName)
|
public static void NotNull<T>(T target, string parameterName)
|
||||||
|
where T : class
|
||||||
{
|
{
|
||||||
if (target == null)
|
if (target == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,7 +22,9 @@ namespace SixLabors
|
||||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
||||||
/// <param name="message">The error message, if any to add to the exception.</param>
|
/// <param name="message">The error message, if any to add to the exception.</param>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null</exception>
|
||||||
public static void NotNull(object target, string parameterName, string message = "")
|
/// <typeparam name="T">The type of the object to verify</typeparam>
|
||||||
|
public static void NotNull<T>(T target, string parameterName, string message = "")
|
||||||
|
where T : class
|
||||||
{
|
{
|
||||||
if (target == null)
|
if (target == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
namespace SixLabors.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options for allocating buffers.
|
||||||
|
/// </summary>
|
||||||
|
public enum AllocationOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the buffer should just be allocated.
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the allocated buffer should be cleaned following allocation.
|
||||||
|
/// </summary>
|
||||||
|
Clean
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using SixLabors.Memory.Internals;
|
||||||
|
|
||||||
|
namespace SixLabors.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains <see cref="Buffer{T}"/> and <see cref="ManagedByteBuffer"/>
|
||||||
|
/// </summary>
|
||||||
|
public partial class ArrayPoolMemoryAllocator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer implementation of <see cref="ArrayPoolMemoryAllocator"/>.
|
||||||
|
/// </summary>
|
||||||
|
private class Buffer<T> : ManagedBufferBase<T>
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The length of the buffer
|
||||||
|
/// </summary>
|
||||||
|
private readonly int length;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A weak reference to the source pool.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// By using a weak reference here, we are making sure that array pools and their retained arrays are always GC-ed
|
||||||
|
/// after a call to <see cref="ArrayPoolMemoryAllocator.ReleaseRetainedResources"/>, regardless of having buffer instances still being in use.
|
||||||
|
/// </remarks>
|
||||||
|
private WeakReference<ArrayPool<byte>> sourcePoolReference;
|
||||||
|
|
||||||
|
public Buffer(byte[] data, int length, ArrayPool<byte> sourcePool)
|
||||||
|
{
|
||||||
|
this.Data = data;
|
||||||
|
this.length = length;
|
||||||
|
this.sourcePoolReference = new WeakReference<ArrayPool<byte>>(sourcePool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the buffer as a byte array.
|
||||||
|
/// </summary>
|
||||||
|
protected byte[] Data { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Span<T> GetSpan() => MemoryMarshal.Cast<byte, T>(this.Data.AsSpan()).Slice(0, this.length);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposing || this.Data == null || this.sourcePoolReference == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sourcePoolReference.TryGetTarget(out ArrayPool<byte> pool))
|
||||||
|
{
|
||||||
|
pool.Return(this.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sourcePoolReference = null;
|
||||||
|
this.Data = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override object GetPinnableObject() => this.Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="IManagedByteBuffer"/> implementation of <see cref="ArrayPoolMemoryAllocator"/>.
|
||||||
|
/// </summary>
|
||||||
|
private class ManagedByteBuffer : Buffer<byte>, IManagedByteBuffer
|
||||||
|
{
|
||||||
|
public ManagedByteBuffer(byte[] data, int length, ArrayPool<byte> sourcePool)
|
||||||
|
: base(data, length, sourcePool)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public byte[] Array => this.Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
namespace SixLabors.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains common factory methods and configuration constants.
|
||||||
|
/// </summary>
|
||||||
|
public partial class ArrayPoolMemoryAllocator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The default value for: maximum size of pooled arrays in bytes.
|
||||||
|
/// Currently set to 24MB, which is equivalent to 8 megapixels of raw RGBA32 data.
|
||||||
|
/// </summary>
|
||||||
|
internal const int DefaultMaxPooledBufferSizeInBytes = 24 * 1024 * 1024;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value for: The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
|
||||||
|
/// </summary>
|
||||||
|
private const int DefaultBufferSelectorThresholdInBytes = 8 * 1024 * 1024;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default bucket count for <see cref="largeArrayPool"/>.
|
||||||
|
/// </summary>
|
||||||
|
private const int DefaultLargePoolBucketCount = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default bucket count for <see cref="normalArrayPool"/>.
|
||||||
|
/// </summary>
|
||||||
|
private const int DefaultNormalPoolBucketCount = 16;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is the default. Should be good for most use cases.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The memory manager</returns>
|
||||||
|
public static ArrayPoolMemoryAllocator CreateDefault()
|
||||||
|
{
|
||||||
|
return new ArrayPoolMemoryAllocator(
|
||||||
|
DefaultMaxPooledBufferSizeInBytes,
|
||||||
|
DefaultBufferSelectorThresholdInBytes,
|
||||||
|
DefaultLargePoolBucketCount,
|
||||||
|
DefaultNormalPoolBucketCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For environments with limited memory capabilities. Only small images are pooled, which can result in reduced througput.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The memory manager</returns>
|
||||||
|
public static ArrayPoolMemoryAllocator CreateWithModeratePooling()
|
||||||
|
{
|
||||||
|
return new ArrayPoolMemoryAllocator(1024 * 1024, 32 * 1024, 16, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Only pool small buffers like image rows.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The memory manager</returns>
|
||||||
|
public static ArrayPoolMemoryAllocator CreateWithMinimalPooling()
|
||||||
|
{
|
||||||
|
return new ArrayPoolMemoryAllocator(64 * 1024, 32 * 1024, 8, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RAM is not an issue for me, gimme maximum througput!
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The memory manager</returns>
|
||||||
|
public static ArrayPoolMemoryAllocator CreateWithAggressivePooling()
|
||||||
|
{
|
||||||
|
return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace SixLabors.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements <see cref="MemoryAllocator"/> by allocating memory from <see cref="ArrayPool{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ArrayPoolMemoryAllocator : MemoryAllocator
|
||||||
|
{
|
||||||
|
private readonly int maxArraysPerBucketNormalPool;
|
||||||
|
|
||||||
|
private readonly int maxArraysPerBucketLargePool;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ArrayPool{T}"/> for small-to-medium buffers which is not kept clean.
|
||||||
|
/// </summary>
|
||||||
|
private ArrayPool<byte> normalArrayPool;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ArrayPool{T}"/> for huge buffers, which is not kept clean.
|
||||||
|
/// </summary>
|
||||||
|
private ArrayPool<byte> largeArrayPool;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public ArrayPoolMemoryAllocator()
|
||||||
|
: this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
|
||||||
|
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes)
|
||||||
|
: this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
|
||||||
|
/// <param name="poolSelectorThresholdInBytes">Arrays over this threshold will be pooled in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
|
||||||
|
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes)
|
||||||
|
: this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
|
||||||
|
/// <param name="poolSelectorThresholdInBytes">The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
|
||||||
|
/// <param name="maxArraysPerBucketLargePool">Max arrays per bucket for the large array pool</param>
|
||||||
|
/// <param name="maxArraysPerBucketNormalPool">Max arrays per bucket for the normal array pool</param>
|
||||||
|
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes, int maxArraysPerBucketLargePool, int maxArraysPerBucketNormalPool)
|
||||||
|
{
|
||||||
|
Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes));
|
||||||
|
Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes));
|
||||||
|
|
||||||
|
this.MaxPoolSizeInBytes = maxPoolSizeInBytes;
|
||||||
|
this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes;
|
||||||
|
this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool;
|
||||||
|
this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool;
|
||||||
|
|
||||||
|
this.InitArrayPools();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the maximum size of pooled arrays in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxPoolSizeInBytes { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
|
||||||
|
/// </summary>
|
||||||
|
public int PoolSelectorThresholdInBytes { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ReleaseRetainedResources()
|
||||||
|
{
|
||||||
|
this.InitArrayPools();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
|
||||||
|
{
|
||||||
|
int itemSizeBytes = Unsafe.SizeOf<T>();
|
||||||
|
int bufferSizeInBytes = length * itemSizeBytes;
|
||||||
|
|
||||||
|
ArrayPool<byte> pool = this.GetArrayPool(bufferSizeInBytes);
|
||||||
|
byte[] byteArray = pool.Rent(bufferSizeInBytes);
|
||||||
|
|
||||||
|
var buffer = new Buffer<T>(byteArray, length, pool);
|
||||||
|
if (options == AllocationOptions.Clean)
|
||||||
|
{
|
||||||
|
buffer.GetSpan().Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None)
|
||||||
|
{
|
||||||
|
ArrayPool<byte> pool = this.GetArrayPool(length);
|
||||||
|
byte[] byteArray = pool.Rent(length);
|
||||||
|
|
||||||
|
var buffer = new ManagedByteBuffer(byteArray, length, pool);
|
||||||
|
if (options == AllocationOptions.Clean)
|
||||||
|
{
|
||||||
|
buffer.GetSpan().Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes)
|
||||||
|
{
|
||||||
|
return maxPoolSizeInBytes / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayPool<byte> GetArrayPool(int bufferSizeInBytes)
|
||||||
|
{
|
||||||
|
return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitArrayPools()
|
||||||
|
{
|
||||||
|
this.largeArrayPool = ArrayPool<byte>.Create(this.MaxPoolSizeInBytes, this.maxArraysPerBucketLargePool);
|
||||||
|
this.normalArrayPool = ArrayPool<byte>.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
|
|
||||||
|
namespace SixLabors.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s.
|
||||||
|
/// </summary>
|
||||||
|
public interface IManagedByteBuffer : IMemoryOwner<byte>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the managed array backing this buffer instance.
|
||||||
|
/// </summary>
|
||||||
|
byte[] Array { get; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace SixLabors.Memory.Internals
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps an array as an <see cref="IManagedByteBuffer"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc />
|
||||||
|
internal class BasicArrayBuffer<T> : ManagedBufferBase<T>
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BasicArrayBuffer{T}"/> class
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">The array</param>
|
||||||
|
/// <param name="length">The length of the buffer</param>
|
||||||
|
public BasicArrayBuffer(T[] array, int length)
|
||||||
|
{
|
||||||
|
DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length));
|
||||||
|
this.Array = array;
|
||||||
|
this.Length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BasicArrayBuffer{T}"/> class
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">The array</param>
|
||||||
|
public BasicArrayBuffer(T[] array)
|
||||||
|
: this(array, array.Length)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the array
|
||||||
|
/// </summary>
|
||||||
|
public T[] Array { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the length
|
||||||
|
/// </summary>
|
||||||
|
public int Length { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Span<T> GetSpan() => this.Array.AsSpan(0, this.Length);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override object GetPinnableObject()
|
||||||
|
{
|
||||||
|
return this.Array;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
namespace SixLabors.Memory.Internals
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an <see cref="IManagedByteBuffer"/> based on <see cref="BasicArrayBuffer{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class BasicByteBuffer : BasicArrayBuffer<byte>, IManagedByteBuffer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BasicByteBuffer"/> class
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">The byte array</param>
|
||||||
|
internal BasicByteBuffer(byte[] array)
|
||||||
|
: base(array)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace SixLabors.Memory.Internals
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a base class for <see cref="IMemoryOwner{T}"/> implementations by implementing pinning logic for <see cref="MemoryManager{T}"/> adaption.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The element type</typeparam>
|
||||||
|
internal abstract class ManagedBufferBase<T> : MemoryManager<T>
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
private GCHandle pinHandle;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override unsafe MemoryHandle Pin(int elementIndex = 0)
|
||||||
|
{
|
||||||
|
if (!this.pinHandle.IsAllocated)
|
||||||
|
{
|
||||||
|
this.pinHandle = GCHandle.Alloc(this.GetPinnableObject(), GCHandleType.Pinned);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ptr = (void*)this.pinHandle.AddrOfPinnedObject();
|
||||||
|
return new MemoryHandle(ptr, this.pinHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Unpin()
|
||||||
|
{
|
||||||
|
if (this.pinHandle.IsAllocated)
|
||||||
|
{
|
||||||
|
this.pinHandle.Free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the object that should be pinned.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The pinnable <see cref="object"/></returns>
|
||||||
|
protected abstract object GetPinnableObject();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
|
|
||||||
|
namespace SixLabors.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Memory managers are used to allocate memory for image processing operations.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MemoryAllocator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Allocates an <see cref="IMemoryOwner{T}" />, holding a <see cref="System.Memory{T}"/> of length <paramref name="length"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the data stored in the buffer</typeparam>
|
||||||
|
/// <param name="length">Size of the buffer to allocate</param>
|
||||||
|
/// <param name="options">The allocation options.</param>
|
||||||
|
/// <returns>A buffer of values of type <typeparamref name="T"/>.</returns>
|
||||||
|
public abstract IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
|
||||||
|
where T : struct;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allocates an <see cref="IManagedByteBuffer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length">The requested buffer length</param>
|
||||||
|
/// <param name="options">The allocation options.</param>
|
||||||
|
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
|
||||||
|
public abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases all retained resources not being in use.
|
||||||
|
/// Eg: by resetting array pools and letting GC to free the arrays.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ReleaseRetainedResources()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
|
using SixLabors.Memory.Internals;
|
||||||
|
|
||||||
|
namespace SixLabors.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements <see cref="MemoryAllocator"/> by newing up arrays by the GC on every allocation requests.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SimpleGcMemoryAllocator : MemoryAllocator
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
|
||||||
|
{
|
||||||
|
return new BasicArrayBuffer<T>(new T[length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None)
|
||||||
|
{
|
||||||
|
return new BasicByteBuffer(new byte[length]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,6 +42,9 @@
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
|
||||||
<PrivateAssets>All</PrivateAssets>
|
<PrivateAssets>All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="System.Buffers" Version="4.5.0" />
|
||||||
|
<PackageReference Include="System.Memory" Version="4.5.1" />
|
||||||
|
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.1'">
|
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.1'">
|
||||||
<PackageReference Include="System.Numerics.Vectors" Version="4.4.0" />
|
<PackageReference Include="System.Numerics.Vectors" Version="4.4.0" />
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=helpers/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
|
@ -39,7 +39,7 @@ namespace SixLabors.Helpers.Tests
|
||||||
{
|
{
|
||||||
Assert.Throws<ArgumentNullException>(() =>
|
Assert.Throws<ArgumentNullException>(() =>
|
||||||
{
|
{
|
||||||
DebugGuard.NotNull(null, "myParamName");
|
DebugGuard.NotNull((object)null, "myParamName");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace SixLabors.Helpers.Tests
|
||||||
{
|
{
|
||||||
Assert.Throws<ArgumentNullException>(() =>
|
Assert.Throws<ArgumentNullException>(() =>
|
||||||
{
|
{
|
||||||
Guard.NotNull(null, "myParamName");
|
Guard.NotNull((object)null, "myParamName");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ namespace SixLabors.Helpers.Tests
|
||||||
{
|
{
|
||||||
var exception = Assert.Throws<ArgumentNullException>(() =>
|
var exception = Assert.Throws<ArgumentNullException>(() =>
|
||||||
{
|
{
|
||||||
Guard.NotNull(null, "myParamName", "myTestMessage");
|
Guard.NotNull((object)null, "myParamName", "myTestMessage");
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.Equal("myParamName", exception.ParamName);
|
Assert.Equal("myParamName", exception.ParamName);
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using SixLabors.Tests;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SixLabors.Memory.Tests
|
||||||
|
{
|
||||||
|
public class ArrayPoolMemoryManagerTests
|
||||||
|
{
|
||||||
|
private const int MaxPooledBufferSizeInBytes = 2048;
|
||||||
|
|
||||||
|
private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2;
|
||||||
|
|
||||||
|
private MemoryAllocator MemoryAllocator { get; set; } =
|
||||||
|
new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location
|
||||||
|
/// </summary>
|
||||||
|
private bool CheckIsRentingPooledBuffer<T>(int length)
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
IMemoryOwner<T> buffer = this.MemoryAllocator.Allocate<T>(length);
|
||||||
|
ref T ptrToPrevPosition0 = ref buffer.GetReference();
|
||||||
|
buffer.Dispose();
|
||||||
|
|
||||||
|
buffer = this.MemoryAllocator.Allocate<T>(length);
|
||||||
|
bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.GetReference());
|
||||||
|
buffer.Dispose();
|
||||||
|
|
||||||
|
return sameBuffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BufferTests : BufferTestSuite
|
||||||
|
{
|
||||||
|
public BufferTests()
|
||||||
|
: base(new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Constructor
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void WhenBothParametersPassedByUser()
|
||||||
|
{
|
||||||
|
var mgr = new ArrayPoolMemoryAllocator(1111, 666);
|
||||||
|
Assert.Equal(1111, mgr.MaxPoolSizeInBytes);
|
||||||
|
Assert.Equal(666, mgr.PoolSelectorThresholdInBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WhenPassedOnly_MaxPooledBufferSizeInBytes_SmallerThresholdValueIsAutoCalculated()
|
||||||
|
{
|
||||||
|
var mgr = new ArrayPoolMemoryAllocator(5000);
|
||||||
|
Assert.Equal(5000, mgr.MaxPoolSizeInBytes);
|
||||||
|
Assert.True(mgr.PoolSelectorThresholdInBytes < mgr.MaxPoolSizeInBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void When_PoolSelectorThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_ExceptionIsThrown()
|
||||||
|
{
|
||||||
|
Assert.ThrowsAny<Exception>(() => { new ArrayPoolMemoryAllocator(100, 200); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(32)]
|
||||||
|
[InlineData(512)]
|
||||||
|
[InlineData(MaxPooledBufferSizeInBytes - 1)]
|
||||||
|
public void SmallBuffersArePooled_OfByte(int size)
|
||||||
|
{
|
||||||
|
Assert.True(this.CheckIsRentingPooledBuffer<byte>(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(128 * 1024 * 1024)]
|
||||||
|
[InlineData(MaxPooledBufferSizeInBytes + 1)]
|
||||||
|
public void LargeBuffersAreNotPooled_OfByte(int size)
|
||||||
|
{
|
||||||
|
if (!TestEnvironment.Is64BitProcess)
|
||||||
|
{
|
||||||
|
// can lead to OutOfMemoryException
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.False(this.CheckIsRentingPooledBuffer<byte>(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public unsafe void SmallBuffersArePooled_OfBigValueType()
|
||||||
|
{
|
||||||
|
int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) - 1;
|
||||||
|
|
||||||
|
Assert.True(this.CheckIsRentingPooledBuffer<LargeStruct>(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public unsafe void LaregeBuffersAreNotPooled_OfBigValueType()
|
||||||
|
{
|
||||||
|
if (!TestEnvironment.Is64BitProcess)
|
||||||
|
{
|
||||||
|
// can lead to OutOfMemoryException
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) + 1;
|
||||||
|
|
||||||
|
Assert.False(this.CheckIsRentingPooledBuffer<LargeStruct>(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(AllocationOptions.None)]
|
||||||
|
[InlineData(AllocationOptions.Clean)]
|
||||||
|
public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options)
|
||||||
|
{
|
||||||
|
using (IMemoryOwner<int> firstAlloc = this.MemoryAllocator.Allocate<int>(42))
|
||||||
|
{
|
||||||
|
firstAlloc.GetSpan().Fill(666);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (IMemoryOwner<int> secondAlloc = this.MemoryAllocator.Allocate<int>(42, options))
|
||||||
|
{
|
||||||
|
int expected = options == AllocationOptions.Clean ? 0 : 666;
|
||||||
|
Assert.Equal(expected, secondAlloc.GetSpan()[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive)
|
||||||
|
{
|
||||||
|
IMemoryOwner<int> buffer = this.MemoryAllocator.Allocate<int>(32);
|
||||||
|
ref int ptrToPrev0 = ref MemoryMarshal.GetReference(buffer.GetSpan());
|
||||||
|
|
||||||
|
if (!keepBufferAlive)
|
||||||
|
{
|
||||||
|
buffer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.MemoryAllocator.ReleaseRetainedResources();
|
||||||
|
|
||||||
|
buffer = this.MemoryAllocator.Allocate<int>(32);
|
||||||
|
|
||||||
|
Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.GetReference()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed()
|
||||||
|
{
|
||||||
|
IMemoryOwner<int> buffer = this.MemoryAllocator.Allocate<int>(32);
|
||||||
|
this.MemoryAllocator.ReleaseRetainedResources();
|
||||||
|
buffer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AllocationOverLargeArrayThreshold_UsesDifferentPool()
|
||||||
|
{
|
||||||
|
if (!TestEnvironment.Is64BitProcess)
|
||||||
|
{
|
||||||
|
// can lead to OutOfMemoryException
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int arrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int);
|
||||||
|
|
||||||
|
IMemoryOwner<int> small = this.MemoryAllocator.Allocate<int>(arrayLengthThreshold - 1);
|
||||||
|
ref int ptr2Small = ref small.GetReference();
|
||||||
|
small.Dispose();
|
||||||
|
|
||||||
|
IMemoryOwner<int> large = this.MemoryAllocator.Allocate<int>(arrayLengthThreshold + 1);
|
||||||
|
|
||||||
|
Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.GetReference()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateWithAggressivePooling()
|
||||||
|
{
|
||||||
|
if (!TestEnvironment.Is64BitProcess)
|
||||||
|
{
|
||||||
|
// can lead to OutOfMemoryException
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling();
|
||||||
|
|
||||||
|
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(4096 * 4096));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateDefault()
|
||||||
|
{
|
||||||
|
if (!TestEnvironment.Is64BitProcess)
|
||||||
|
{
|
||||||
|
// can lead to OutOfMemoryException
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault();
|
||||||
|
|
||||||
|
Assert.False(this.CheckIsRentingPooledBuffer<Rgba32>(2 * 4096 * 4096));
|
||||||
|
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(2048 * 2048));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateWithModeratePooling()
|
||||||
|
{
|
||||||
|
if (!TestEnvironment.Is64BitProcess)
|
||||||
|
{
|
||||||
|
// can lead to OutOfMemoryException
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling();
|
||||||
|
|
||||||
|
Assert.False(this.CheckIsRentingPooledBuffer<Rgba32>(2048 * 2048));
|
||||||
|
Assert.True(this.CheckIsRentingPooledBuffer<Rgba32>(1024 * 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct Rgba32
|
||||||
|
{
|
||||||
|
private uint dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = MaxPooledBufferSizeInBytes / 5)]
|
||||||
|
private struct LargeStruct
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace SixLabors.Memory.Tests
|
||||||
|
{
|
||||||
|
internal static class BufferExtensions
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Span<T> GetSpan<T>(this IMemoryOwner<T> buffer)
|
||||||
|
=> buffer.Memory.Span;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int Length<T>(this IMemoryOwner<T> buffer)
|
||||||
|
=> buffer.GetSpan().Length;
|
||||||
|
|
||||||
|
public static ref T GetReference<T>(this IMemoryOwner<T> buffer)
|
||||||
|
where T : struct =>
|
||||||
|
ref MemoryMarshal.GetReference(buffer.GetSpan());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,318 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
namespace SixLabors.Memory.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Inherit this class to test an <see cref="IMemoryOwner{T}"/> implementation (provided by <see cref="MemoryAllocator"/>).
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BufferTestSuite
|
||||||
|
{
|
||||||
|
protected BufferTestSuite(MemoryAllocator memoryAllocator)
|
||||||
|
{
|
||||||
|
this.MemoryAllocator = memoryAllocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MemoryAllocator MemoryAllocator { get; }
|
||||||
|
|
||||||
|
public struct CustomStruct : IEquatable<CustomStruct>
|
||||||
|
{
|
||||||
|
public long A;
|
||||||
|
|
||||||
|
public byte B;
|
||||||
|
|
||||||
|
public float C;
|
||||||
|
|
||||||
|
public CustomStruct(long a, byte b, float c)
|
||||||
|
{
|
||||||
|
this.A = a;
|
||||||
|
this.B = b;
|
||||||
|
this.C = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(CustomStruct other)
|
||||||
|
{
|
||||||
|
return this.A == other.A && this.B == other.B && this.C.Equals(other.C);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is CustomStruct other && this.Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
int hashCode = this.A.GetHashCode();
|
||||||
|
hashCode = (hashCode * 397) ^ this.B.GetHashCode();
|
||||||
|
hashCode = (hashCode * 397) ^ this.C.GetHashCode();
|
||||||
|
return hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly TheoryData<int> LenthValues = new TheoryData<int> { 0, 1, 7, 1023, 1024 };
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void HasCorrectLength_byte(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestHasCorrectLength<byte>(desiredLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void HasCorrectLength_float(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestHasCorrectLength<float>(desiredLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void HasCorrectLength_CustomStruct(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestHasCorrectLength<CustomStruct>(desiredLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TestHasCorrectLength<T>(int desiredLength)
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
using (IMemoryOwner<T> buffer = this.MemoryAllocator.Allocate<T>(desiredLength))
|
||||||
|
{
|
||||||
|
Assert.Equal(desiredLength, buffer.GetSpan().Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void CanAllocateCleanBuffer_byte(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestCanAllocateCleanBuffer<byte>(desiredLength, false);
|
||||||
|
this.TestCanAllocateCleanBuffer<byte>(desiredLength, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void CanAllocateCleanBuffer_double(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestCanAllocateCleanBuffer<double>(desiredLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void CanAllocateCleanBuffer_CustomStruct(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestCanAllocateCleanBuffer<CustomStruct>(desiredLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IMemoryOwner<T> Allocate<T>(int desiredLength, AllocationOptions options, bool managedByteBuffer)
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
if (managedByteBuffer)
|
||||||
|
{
|
||||||
|
if (!(this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength, options) is IMemoryOwner<T> buffer))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("typeof(T) != typeof(byte)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.MemoryAllocator.Allocate<T>(desiredLength, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TestCanAllocateCleanBuffer<T>(int desiredLength, bool testManagedByteBuffer = false)
|
||||||
|
where T : struct, IEquatable<T>
|
||||||
|
{
|
||||||
|
ReadOnlySpan<T> expected = new T[desiredLength];
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
using (IMemoryOwner<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.Clean, testManagedByteBuffer))
|
||||||
|
{
|
||||||
|
Assert.True(buffer.GetSpan().SequenceEqual(expected));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void SpanPropertyIsAlwaysTheSame_int(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestSpanPropertyIsAlwaysTheSame<int>(desiredLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestSpanPropertyIsAlwaysTheSame<byte>(desiredLength, false);
|
||||||
|
this.TestSpanPropertyIsAlwaysTheSame<byte>(desiredLength, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TestSpanPropertyIsAlwaysTheSame<T>(int desiredLength, bool testManagedByteBuffer = false)
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
using (IMemoryOwner<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.None, testManagedByteBuffer))
|
||||||
|
{
|
||||||
|
ref T a = ref MemoryMarshal.GetReference(buffer.GetSpan());
|
||||||
|
ref T b = ref MemoryMarshal.GetReference(buffer.GetSpan());
|
||||||
|
ref T c = ref MemoryMarshal.GetReference(buffer.GetSpan());
|
||||||
|
|
||||||
|
Assert.True(Unsafe.AreSame(ref a, ref b));
|
||||||
|
Assert.True(Unsafe.AreSame(ref b, ref c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void WriteAndReadElements_float(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestWriteAndReadElements<float>(desiredLength, x => x * 1.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void WriteAndReadElements_byte(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestWriteAndReadElements<byte>(desiredLength, x => (byte)(x + 1), false);
|
||||||
|
this.TestWriteAndReadElements<byte>(desiredLength, x => (byte)(x + 1), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TestWriteAndReadElements<T>(int desiredLength, Func<int, T> getExpectedValue, bool testManagedByteBuffer = false)
|
||||||
|
where T : struct
|
||||||
|
{
|
||||||
|
using (IMemoryOwner<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.None, testManagedByteBuffer))
|
||||||
|
{
|
||||||
|
T[] expectedVals = new T[buffer.Length()];
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.Length(); i++)
|
||||||
|
{
|
||||||
|
Span<T> span = buffer.GetSpan();
|
||||||
|
expectedVals[i] = getExpectedValue(i);
|
||||||
|
span[i] = expectedVals[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.Length(); i++)
|
||||||
|
{
|
||||||
|
Span<T> span = buffer.GetSpan();
|
||||||
|
Assert.Equal(expectedVals[i], span[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestIndexOutOfRangeShouldThrow<byte>(desiredLength, false);
|
||||||
|
this.TestIndexOutOfRangeShouldThrow<byte>(desiredLength, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestIndexOutOfRangeShouldThrow<long>(desiredLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(LenthValues))]
|
||||||
|
public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength)
|
||||||
|
{
|
||||||
|
this.TestIndexOutOfRangeShouldThrow<CustomStruct>(desiredLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private T TestIndexOutOfRangeShouldThrow<T>(int desiredLength, bool testManagedByteBuffer = false)
|
||||||
|
where T : struct, IEquatable<T>
|
||||||
|
{
|
||||||
|
var dummy = default(T);
|
||||||
|
|
||||||
|
using (IMemoryOwner<T> buffer = this.Allocate<T>(desiredLength, AllocationOptions.None, testManagedByteBuffer))
|
||||||
|
{
|
||||||
|
Assert.ThrowsAny<Exception>(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
Span<T> span = buffer.GetSpan();
|
||||||
|
dummy = span[desiredLength];
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.ThrowsAny<Exception>(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
Span<T> span = buffer.GetSpan();
|
||||||
|
dummy = span[desiredLength + 1];
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.ThrowsAny<Exception>(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
Span<T> span = buffer.GetSpan();
|
||||||
|
dummy = span[desiredLength + 42];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(7)]
|
||||||
|
[InlineData(1024)]
|
||||||
|
[InlineData(6666)]
|
||||||
|
public void ManagedByteBuffer_ArrayIsCorrect(int desiredLength)
|
||||||
|
{
|
||||||
|
using (IManagedByteBuffer buffer = this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength))
|
||||||
|
{
|
||||||
|
ref byte array0 = ref buffer.Array[0];
|
||||||
|
ref byte span0 = ref buffer.GetReference();
|
||||||
|
|
||||||
|
Assert.True(Unsafe.AreSame(ref span0, ref array0));
|
||||||
|
Assert.True(buffer.Array.Length >= buffer.GetSpan().Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMemory_ReturnsValidMemory()
|
||||||
|
{
|
||||||
|
using (IMemoryOwner<CustomStruct> buffer = this.MemoryAllocator.Allocate<CustomStruct>(42))
|
||||||
|
{
|
||||||
|
Span<CustomStruct> span0 = buffer.GetSpan();
|
||||||
|
span0[10].A = 30;
|
||||||
|
Memory<CustomStruct> memory = buffer.Memory;
|
||||||
|
|
||||||
|
Assert.Equal(42, memory.Length);
|
||||||
|
Span<CustomStruct> span1 = memory.Span;
|
||||||
|
|
||||||
|
Assert.Equal(42, span1.Length);
|
||||||
|
Assert.Equal(30, span1[10].A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public unsafe void GetMemory_ResultIsPinnable()
|
||||||
|
{
|
||||||
|
using (IMemoryOwner<int> buffer = this.MemoryAllocator.Allocate<int>(42))
|
||||||
|
{
|
||||||
|
Span<int> span0 = buffer.GetSpan();
|
||||||
|
span0[10] = 30;
|
||||||
|
|
||||||
|
Memory<int> memory = buffer.Memory;
|
||||||
|
|
||||||
|
using (MemoryHandle h = memory.Pin())
|
||||||
|
{
|
||||||
|
int* ptr = (int*)h.Pointer;
|
||||||
|
Assert.Equal(30, ptr[10]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
namespace SixLabors.Memory.Tests
|
||||||
|
{
|
||||||
|
public class SimpleGcMemoryManagerTests
|
||||||
|
{
|
||||||
|
public class BufferTests : BufferTestSuite
|
||||||
|
{
|
||||||
|
public BufferTests()
|
||||||
|
: base(new SimpleGcMemoryAllocator())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
|
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
|
||||||
<DebugType Condition="$(codecov) != ''">full</DebugType>
|
<DebugType Condition="$(codecov) != ''">full</DebugType>
|
||||||
<RootNamespace>SixLabors.Tests</RootNamespace>
|
<RootNamespace>SixLabors.Tests</RootNamespace>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright (c) Six Labors and contributors.
|
||||||
|
// Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SixLabors.Tests
|
||||||
|
{
|
||||||
|
internal class TestEnvironment
|
||||||
|
{
|
||||||
|
internal static bool Is64BitProcess => IntPtr.Size == 8;
|
||||||
|
}
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче