mirror of https://github.com/SixLabors/ImageSharp
20 changed files with 87 additions and 1360 deletions
@ -1,21 +0,0 @@ |
|||
// 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 |
|||
} |
|||
} |
|||
@ -1,82 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
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 />
|
|||
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; |
|||
} |
|||
|
|||
public override Span<T> GetSpan() => MemoryMarshal.Cast<byte, T>(this.Data.AsSpan()).Slice(0, this.length); |
|||
|
|||
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) |
|||
{ |
|||
} |
|||
|
|||
public byte[] Array => this.Data; |
|||
} |
|||
} |
|||
} |
|||
@ -1,71 +0,0 @@ |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
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 <see cref="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); |
|||
} |
|||
} |
|||
} |
|||
@ -1,140 +0,0 @@ |
|||
// 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 |
|||
{ |
|||
/// <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; |
|||
|
|||
private readonly int maxArraysPerBucketNormalPool; |
|||
|
|||
private readonly int maxArraysPerBucketLargePool; |
|||
|
|||
/// <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) |
|||
{ |
|||
ImageSharp.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 />
|
|||
internal 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.Clear(); |
|||
} |
|||
|
|||
return buffer; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options) |
|||
{ |
|||
ArrayPool<byte> pool = this.GetArrayPool(length); |
|||
byte[] byteArray = pool.Rent(length); |
|||
|
|||
var buffer = new ManagedByteBuffer(byteArray, length, pool); |
|||
if (options == AllocationOptions.Clean) |
|||
{ |
|||
buffer.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); |
|||
} |
|||
} |
|||
} |
|||
@ -1,59 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Wraps an array as an <see cref="IManagedByteBuffer"/> instance.
|
|||
/// </summary>
|
|||
internal class BasicArrayBuffer<T> : ManagedBufferBase<T> |
|||
where T : struct |
|||
{ |
|||
public BasicArrayBuffer(T[] array, int length) |
|||
{ |
|||
ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); |
|||
this.Array = array; |
|||
this.Length = length; |
|||
} |
|||
|
|||
public BasicArrayBuffer(T[] array) |
|||
: this(array, array.Length) |
|||
{ |
|||
} |
|||
|
|||
public T[] Array { get; } |
|||
|
|||
public int Length { get; } |
|||
|
|||
/// <summary>
|
|||
/// Returns a reference to specified element of the buffer.
|
|||
/// </summary>
|
|||
/// <param name="index">The index</param>
|
|||
/// <returns>The reference to the specified element</returns>
|
|||
public ref T this[int index] |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get |
|||
{ |
|||
DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); |
|||
|
|||
Span<T> span = this.GetSpan(); |
|||
return ref span[index]; |
|||
} |
|||
} |
|||
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
} |
|||
|
|||
public override Span<T> GetSpan() => this.Array.AsSpan(0, this.Length); |
|||
|
|||
protected override object GetPinnableObject() |
|||
{ |
|||
return this.Array; |
|||
} |
|||
} |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
internal sealed class BasicByteBuffer : BasicArrayBuffer<byte>, IManagedByteBuffer |
|||
{ |
|||
internal BasicByteBuffer(byte[] array) |
|||
: base(array) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
// 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>
|
|||
internal interface IManagedByteBuffer : IMemoryOwner<byte> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the managed array backing this buffer instance.
|
|||
/// </summary>
|
|||
byte[] Array { get; } |
|||
} |
|||
} |
|||
@ -1,43 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Buffers; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Provides a base class for <see cref="IMemoryOwner{T}"/> implementations by implementing pinning logic for <see cref="MemoryManager{T}"/> adaption.
|
|||
/// </summary>
|
|||
internal abstract class ManagedBufferBase<T> : MemoryManager<T> |
|||
where T : struct |
|||
{ |
|||
private GCHandle pinHandle; |
|||
|
|||
public bool IsMemoryOwner => true; |
|||
|
|||
/// <summary>
|
|||
/// Gets the object that should be pinned.
|
|||
/// </summary>
|
|||
protected abstract object GetPinnableObject(); |
|||
|
|||
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); |
|||
} |
|||
|
|||
public override void Unpin() |
|||
{ |
|||
if (this.pinHandle.IsAllocated) |
|||
{ |
|||
this.pinHandle.Free(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,39 +0,0 @@ |
|||
// 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>
|
|||
internal 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>
|
|||
internal 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() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
using System.Buffers; |
|||
|
|||
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 />
|
|||
internal override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None) |
|||
{ |
|||
return new BasicArrayBuffer<T>(new T[length]); |
|||
} |
|||
|
|||
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options) |
|||
{ |
|||
return new BasicByteBuffer(new byte[length]); |
|||
} |
|||
} |
|||
} |
|||
@ -1,239 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System.Buffers; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory |
|||
{ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using SixLabors.Memory; |
|||
|
|||
using Xunit; |
|||
|
|||
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); }); |
|||
} |
|||
} |
|||
|
|||
[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<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)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,320 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.Memory; |
|||
using Xunit; |
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory |
|||
{ |
|||
using System.Buffers; |
|||
|
|||
/// <summary>
|
|||
/// Inherit this class to test an <see cref="IBuffer{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]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,15 +0,0 @@ |
|||
namespace SixLabors.ImageSharp.Tests.Memory |
|||
{ |
|||
using SixLabors.Memory; |
|||
|
|||
public class SimpleGcMemoryManagerTests |
|||
{ |
|||
public class BufferTests : BufferTestSuite |
|||
{ |
|||
public BufferTests() |
|||
: base(new SimpleGcMemoryAllocator()) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,263 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
// ReSharper disable AccessToStaticMemberViaDerivedType
|
|||
namespace SixLabors.ImageSharp.Tests.Memory |
|||
{ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using Xunit; |
|||
|
|||
public unsafe class SpanUtilityTests |
|||
{ |
|||
// ReSharper disable once ClassNeverInstantiated.Local
|
|||
private class Assert : Xunit.Assert |
|||
{ |
|||
public static void SameRefs<T1, T2>(ref T1 a, ref T2 b) |
|||
{ |
|||
ref T1 bb = ref Unsafe.As<T2, T1>(ref b); |
|||
|
|||
Assert.True(Unsafe.AreSame(ref a, ref bb), "References are not same!"); |
|||
} |
|||
} |
|||
|
|||
public class SpanHelper_Copy |
|||
{ |
|||
private static void AssertNotDefault<T>(T[] data, int idx) |
|||
where T : struct |
|||
{ |
|||
Assert.NotEqual(default, data[idx]); |
|||
} |
|||
|
|||
private static byte[] CreateTestBytes(int count) |
|||
{ |
|||
byte[] result = new byte[count]; |
|||
for (int i = 0; i < result.Length; i++) |
|||
{ |
|||
result[i] = (byte)((i % 200) + 1); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
private static int[] CreateTestInts(int count) |
|||
{ |
|||
int[] result = new int[count]; |
|||
for (int i = 0; i < result.Length; i++) |
|||
{ |
|||
result[i] = i + 1; |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(4)] |
|||
[InlineData(1500)] |
|||
public void GenericToOwnType(int count) |
|||
{ |
|||
TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2); |
|||
var dest = new TestStructs.Foo[count + 5]; |
|||
|
|||
var apSource = new Span<TestStructs.Foo>(source, 1, source.Length - 1); |
|||
var apDest = new Span<TestStructs.Foo>(dest, 1, dest.Length - 1); |
|||
|
|||
apSource.Slice(0, count - 1).CopyTo(apDest); |
|||
|
|||
AssertNotDefault(source, 1); |
|||
AssertNotDefault(dest, 1); |
|||
|
|||
Assert.NotEqual(source[0], dest[0]); |
|||
Assert.Equal(source[1], dest[1]); |
|||
Assert.Equal(source[2], dest[2]); |
|||
Assert.Equal(source[count - 1], dest[count - 1]); |
|||
Assert.NotEqual(source[count], dest[count]); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(4)] |
|||
[InlineData(1500)] |
|||
public void GenericToOwnType_Aligned(int count) |
|||
{ |
|||
TestStructs.AlignedFoo[] source = TestStructs.AlignedFoo.CreateArray(count + 2); |
|||
var dest = new TestStructs.AlignedFoo[count + 5]; |
|||
|
|||
var apSource = new Span<TestStructs.AlignedFoo>(source, 1, source.Length - 1); |
|||
var apDest = new Span<TestStructs.AlignedFoo>(dest, 1, dest.Length - 1); |
|||
|
|||
apSource.Slice(0, count - 1).CopyTo(apDest); |
|||
|
|||
AssertNotDefault(source, 1); |
|||
AssertNotDefault(dest, 1); |
|||
|
|||
Assert.NotEqual(source[0], dest[0]); |
|||
Assert.Equal(source[1], dest[1]); |
|||
Assert.Equal(source[2], dest[2]); |
|||
Assert.Equal(source[count - 1], dest[count - 1]); |
|||
Assert.NotEqual(source[count], dest[count]); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(4)] |
|||
[InlineData(1500)] |
|||
public void IntToInt(int count) |
|||
{ |
|||
int[] source = CreateTestInts(count + 2); |
|||
int[] dest = new int[count + 5]; |
|||
|
|||
var apSource = new Span<int>(source, 1, source.Length - 1); |
|||
var apDest = new Span<int>(dest, 1, dest.Length - 1); |
|||
|
|||
apSource.Slice(0, count - 1).CopyTo(apDest); |
|||
|
|||
AssertNotDefault(source, 1); |
|||
AssertNotDefault(dest, 1); |
|||
|
|||
Assert.NotEqual(source[0], dest[0]); |
|||
Assert.Equal(source[1], dest[1]); |
|||
Assert.Equal(source[2], dest[2]); |
|||
Assert.Equal(source[count - 1], dest[count - 1]); |
|||
Assert.NotEqual(source[count], dest[count]); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(4)] |
|||
[InlineData(1500)] |
|||
public void GenericToBytes(int count) |
|||
{ |
|||
int destCount = count * sizeof(TestStructs.Foo); |
|||
TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2); |
|||
byte[] dest = new byte[destCount + sizeof(TestStructs.Foo) * 2]; |
|||
|
|||
var apSource = new Span<TestStructs.Foo>(source, 1, source.Length - 1); |
|||
var apDest = new Span<byte>(dest, sizeof(TestStructs.Foo), dest.Length - sizeof(TestStructs.Foo)); |
|||
|
|||
MemoryMarshal.AsBytes(apSource).Slice(0, (count - 1) * sizeof(TestStructs.Foo)).CopyTo(apDest); |
|||
|
|||
AssertNotDefault(source, 1); |
|||
|
|||
Assert.False((bool)ElementsAreEqual(source, dest, 0)); |
|||
Assert.True((bool)ElementsAreEqual(source, dest, 1)); |
|||
Assert.True((bool)ElementsAreEqual(source, dest, 2)); |
|||
Assert.True((bool)ElementsAreEqual(source, dest, count - 1)); |
|||
Assert.False((bool)ElementsAreEqual(source, dest, count)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(4)] |
|||
[InlineData(1500)] |
|||
public void GenericToBytes_Aligned(int count) |
|||
{ |
|||
int destCount = count * sizeof(TestStructs.Foo); |
|||
TestStructs.AlignedFoo[] source = TestStructs.AlignedFoo.CreateArray(count + 2); |
|||
byte[] dest = new byte[destCount + sizeof(TestStructs.AlignedFoo) * 2]; |
|||
|
|||
var apSource = new Span<TestStructs.AlignedFoo>(source, 1, source.Length - 1); |
|||
var apDest = new Span<byte>(dest, sizeof(TestStructs.AlignedFoo), dest.Length - sizeof(TestStructs.AlignedFoo)); |
|||
|
|||
MemoryMarshal.AsBytes(apSource).Slice(0, (count - 1) * sizeof(TestStructs.AlignedFoo)).CopyTo(apDest); |
|||
|
|||
AssertNotDefault(source, 1); |
|||
|
|||
Assert.False((bool)ElementsAreEqual(source, dest, 0)); |
|||
Assert.True((bool)ElementsAreEqual(source, dest, 1)); |
|||
Assert.True((bool)ElementsAreEqual(source, dest, 2)); |
|||
Assert.True((bool)ElementsAreEqual(source, dest, count - 1)); |
|||
Assert.False((bool)ElementsAreEqual(source, dest, count)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(4)] |
|||
[InlineData(1500)] |
|||
public void IntToBytes(int count) |
|||
{ |
|||
int destCount = count * sizeof(int); |
|||
int[] source = CreateTestInts(count + 2); |
|||
byte[] dest = new byte[destCount + sizeof(int) + 1]; |
|||
|
|||
var apSource = new Span<int>(source); |
|||
var apDest = new Span<byte>(dest); |
|||
|
|||
MemoryMarshal.AsBytes(apSource).Slice(0, count * sizeof(int)).CopyTo(apDest); |
|||
|
|||
AssertNotDefault(source, 1); |
|||
|
|||
Assert.True((bool)ElementsAreEqual(source, dest, 0)); |
|||
Assert.True((bool)ElementsAreEqual(source, dest, count - 1)); |
|||
Assert.False((bool)ElementsAreEqual(source, dest, count)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(4)] |
|||
[InlineData(1500)] |
|||
public void BytesToGeneric(int count) |
|||
{ |
|||
int srcCount = count * sizeof(TestStructs.Foo); |
|||
byte[] source = CreateTestBytes(srcCount); |
|||
var dest = new TestStructs.Foo[count + 2]; |
|||
|
|||
var apSource = new Span<byte>(source); |
|||
var apDest = new Span<TestStructs.Foo>(dest); |
|||
|
|||
apSource.Slice(0, count * sizeof(TestStructs.Foo)).CopyTo(MemoryMarshal.AsBytes(apDest)); |
|||
|
|||
AssertNotDefault(source, sizeof(TestStructs.Foo) + 1); |
|||
AssertNotDefault(dest, 1); |
|||
|
|||
Assert.True((bool)ElementsAreEqual(dest, source, 0)); |
|||
Assert.True((bool)ElementsAreEqual(dest, source, 1)); |
|||
Assert.True((bool)ElementsAreEqual(dest, source, count - 1)); |
|||
|
|||
// Difference is 2.4380727671472639E-289
|
|||
// 32 bit doesn't compare accuarately enough and is blocking our PR's
|
|||
// TODO: Refactor a better test.
|
|||
if (Environment.Is64BitProcess) |
|||
{ |
|||
Assert.False((bool)ElementsAreEqual(dest, source, count)); |
|||
} |
|||
} |
|||
|
|||
internal static bool ElementsAreEqual(TestStructs.Foo[] array, byte[] rawArray, int index) |
|||
{ |
|||
fixed (TestStructs.Foo* pArray = array) |
|||
fixed (byte* pRaw = rawArray) |
|||
{ |
|||
var pCasted = (TestStructs.Foo*)pRaw; |
|||
|
|||
TestStructs.Foo val1 = pArray[index]; |
|||
TestStructs.Foo val2 = pCasted[index]; |
|||
|
|||
return val1.Equals(val2); |
|||
} |
|||
} |
|||
|
|||
internal static bool ElementsAreEqual(TestStructs.AlignedFoo[] array, byte[] rawArray, int index) |
|||
{ |
|||
fixed (TestStructs.AlignedFoo* pArray = array) |
|||
fixed (byte* pRaw = rawArray) |
|||
{ |
|||
var pCasted = (TestStructs.AlignedFoo*)pRaw; |
|||
|
|||
TestStructs.AlignedFoo val1 = pArray[index]; |
|||
TestStructs.AlignedFoo val2 = pCasted[index]; |
|||
|
|||
return val1.Equals(val2); |
|||
} |
|||
} |
|||
|
|||
internal static bool ElementsAreEqual(int[] array, byte[] rawArray, int index) |
|||
{ |
|||
fixed (int* pArray = array) |
|||
fixed (byte* pRaw = rawArray) |
|||
{ |
|||
int* pCasted = (int*)pRaw; |
|||
|
|||
int val1 = pArray[index]; |
|||
int val2 = pCasted[index]; |
|||
|
|||
return val1.Equals(val2); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue