diff options
Diffstat (limited to 'lib/Utils')
-rw-r--r-- | lib/Utils/src/Extensions/MemoryExtensions.cs | 102 | ||||
-rw-r--r-- | lib/Utils/src/IO/VnMemoryStream.cs | 4 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryUtilAlloc.cs | 378 | ||||
-rw-r--r-- | lib/Utils/src/Memory/UnmanagedHeapBase.cs | 16 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/MemoryUtilTests.cs | 6 |
5 files changed, 285 insertions, 221 deletions
diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs index c433527..99d4cf1 100644 --- a/lib/Utils/src/Extensions/MemoryExtensions.cs +++ b/lib/Utils/src/Extensions/MemoryExtensions.cs @@ -48,19 +48,8 @@ namespace VNLib.Utils.Extensions /// <param name="size">The minimum size array to allocate</param> /// <param name="zero">Should elements from 0 to size be set to default(T)</param> /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns> - public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged - { - ArgumentNullException.ThrowIfNull(pool); - - T[] array = pool.Rent(size); - - if (zero) - { - MemoryUtil.InitializeBlock(array, (uint)size); - } - - return new(pool, array, size); - } + public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged + => MemoryUtil.UnsafeAlloc<T>(pool, size, zero); /// <summary> /// Rents a new array and stores it as a resource within an <see cref="OpenResourceHandle{T}"/> to return the @@ -72,19 +61,7 @@ namespace VNLib.Utils.Extensions /// <param name="zero">Should elements from 0 to size be set to default(T)</param> /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns> public static IMemoryHandle<T> SafeAlloc<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : struct - { - ArgumentNullException.ThrowIfNull(pool); - - T[] array = pool.Rent(size); - - if (zero) - { - MemoryUtil.InitializeBlock(array, (uint)size); - } - - //Use the array pool buffer wrapper to return the array to the pool when the handle is disposed - return new ArrayPoolBuffer<T>(pool, array, size); - } + => MemoryUtil.SafeAlloc<T>(pool, size, zero); /// <summary> /// Retreives a buffer that is at least the reqested length, and clears the array from 0-size. @@ -111,13 +88,6 @@ namespace VNLib.Utils.Extensions } /// <summary> - /// Copies the characters within the memory handle to a <see cref="string"/> - /// </summary> - /// <returns>The string representation of the buffer</returns> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ToString<T>(this T charBuffer) where T : IMemoryHandle<char> => charBuffer.Span.ToString(); - - /// <summary> /// Wraps the <see cref="IMemoryHandle{T}"/> instance in System.Buffers.MemoryManager /// wrapper to provide <see cref="Memory{T}"/> buffers from umanaged handles. /// </summary> @@ -131,7 +101,8 @@ namespace VNLib.Utils.Extensions /// <remarks>NOTE: This wrapper now manages the lifetime of the current handle</remarks> /// <exception cref="ArgumentNullException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryManager<T> ToMemoryManager<T>(this IMemoryHandle<T> handle, bool ownsHandle) => new SysBufferMemoryManager<T>(handle, ownsHandle); + public static MemoryManager<T> ToMemoryManager<T>(this IMemoryHandle<T> handle, bool ownsHandle) + => new SysBufferMemoryManager<T>(handle, ownsHandle); /// <summary> /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="IUnmangedHeap"/> instance @@ -168,7 +139,8 @@ namespace VNLib.Utils.Extensions /// </returns> /// <exception cref="OverflowException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetIntLength<T>(this IMemoryHandle<T> handle) => Convert.ToInt32(handle.Length); + public static int GetIntLength<T>(this IMemoryHandle<T> handle) + => Convert.ToInt32(handle.Length); /// <summary> /// Gets the integer length (number of elements) of the <see cref="UnsafeMemoryHandle{T}"/> @@ -181,7 +153,8 @@ namespace VNLib.Utils.Extensions /// </returns> //Method only exists for consistancy since unsafe handles are always 32bit [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetIntLength<T>(this in UnsafeMemoryHandle<T> handle) where T : unmanaged => handle.IntLength; + public static int GetIntLength<T>(this in UnsafeMemoryHandle<T> handle) where T : unmanaged + => handle.IntLength; /// <summary> /// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks @@ -440,16 +413,8 @@ namespace VNLib.Utils.Extensions /// <exception cref="ArgumentException"></exception> /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ObjectDisposedException"></exception> - public static unsafe MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged - { - ArgumentNullException.ThrowIfNull(heap); - //Minimum of one element - elements = Math.Max(elements, 1); - //If zero flag is set then specify zeroing memory - IntPtr block = heap.Alloc(elements, (nuint)sizeof(T), zero); - //Return handle wrapper - return new MemoryHandle<T>(heap, block, elements, zero); - } + public static unsafe MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged + => MemoryUtil.SafeAlloc<T>(heap, elements, zero); /// <summary> /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type @@ -464,10 +429,7 @@ namespace VNLib.Utils.Extensions /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nint elements, bool zero = false) where T : unmanaged - { - ArgumentOutOfRangeException.ThrowIfNegative(elements); - return Alloc<T>(heap, (nuint)elements, zero); - } + => MemoryUtil.SafeAlloc<T>(heap, elements, zero); /// <summary> /// Allocates a buffer from the current heap and initialzies it by copying the initial data buffer @@ -481,10 +443,8 @@ namespace VNLib.Utils.Extensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlySpan<T> initialData) where T : unmanaged { - //Aloc block - MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length); - - //Copy initial data + MemoryHandle<T> handle = Alloc<T>(heap, initialData.Length); + MemoryUtil.Copy(initialData, 0, handle, 0, initialData.Length); return handle; @@ -502,10 +462,8 @@ namespace VNLib.Utils.Extensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlyMemory<T> initialData) where T : unmanaged { - //Aloc block - MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length); - - //Copy initial data + MemoryHandle<T> handle = Alloc<T>(heap, initialData.Length); + MemoryUtil.Copy(initialData, 0, handle, 0, initialData.Length); return handle; @@ -542,25 +500,8 @@ namespace VNLib.Utils.Extensions /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ObjectDisposedException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged - { - ArgumentNullException.ThrowIfNull(heap); - - if (elements < 1) - { - //Return an empty handle - return new UnsafeMemoryHandle<T>(); - } - - //Get element size - nuint elementSize = (nuint)Unsafe.SizeOf<T>(); - - //If zero flag is set then specify zeroing memory (safe case because of the above check) - IntPtr block = heap.Alloc((nuint)elements, elementSize, zero); - - //handle wrapper - return new (heap, block, elements); - } + public unsafe static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged + => MemoryUtil.UnsafeAlloc<T>(heap, elements, zero); #region VnBufferWriter @@ -775,13 +716,6 @@ namespace VNLib.Utils.Extensions } /// <summary> - /// Converts the buffer data to a <see cref="PrivateString"/> - /// </summary> - /// <returns>A <see cref="PrivateString"/> instance that owns the underlying string memory</returns> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PrivateString ToPrivate(this ref ForwardOnlyWriter<char> buffer) => new(buffer.ToString(), true); - - /// <summary> /// Gets a <see cref="Span{T}"/> over the modified section of the internal buffer /// </summary> /// <returns>A <see cref="Span{T}"/> over the modified data</returns> diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs index 2e5ec40..23df869 100644 --- a/lib/Utils/src/IO/VnMemoryStream.cs +++ b/lib/Utils/src/IO/VnMemoryStream.cs @@ -41,6 +41,8 @@ namespace VNLib.Utils.IO /// </summary> public sealed class VnMemoryStream : Stream, ICloneable { + public const int DefaultBufferSize = 4096; + private nint _position; private nint _length; private bool _isReadonly; @@ -108,7 +110,7 @@ namespace VNLib.Utils.IO /// <param name="heap"><see cref="Win32PrivateHeap"/> to allocate memory from</param> /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ArgumentNullException"></exception> - public VnMemoryStream(IUnmangedHeap heap) : this(heap, 0, false) { } + public VnMemoryStream(IUnmangedHeap heap) : this(heap, DefaultBufferSize, false) { } /// <summary> /// Creates a new memory stream and pre-allocates the internal diff --git a/lib/Utils/src/Memory/MemoryUtilAlloc.cs b/lib/Utils/src/Memory/MemoryUtilAlloc.cs index e2e7434..6e4f9b0 100644 --- a/lib/Utils/src/Memory/MemoryUtilAlloc.cs +++ b/lib/Utils/src/Memory/MemoryUtilAlloc.cs @@ -27,14 +27,83 @@ using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; -using VNLib.Utils.Extensions; - namespace VNLib.Utils.Memory { public static unsafe partial class MemoryUtil { #region alloc + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UseUnmanagedHeap<T>(IUnmangedHeap heap, nuint elements) + { + /* + * We may allocate from the share heap only if the heap is not using locks + * or if the element size could cause performance issues because its too large + * to use a managed array. + * + * We want to avoid allocations, that may end up in the LOH if we can + */ + + return (heap.CreationFlags & HeapCreation.UseSynchronization) == 0 + || ByteCount<T>((uint)elements) > MAX_UNSAFE_POOL_SIZE; + } + + /// <summary> + /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type + /// </summary> + /// <typeparam name="T">Unmanaged data type to create a block of</typeparam> + /// <param name="heap"></param> + /// <param name="elements">The size of the block (number of elements)</param> + /// <param name="zero">A flag that zeros the allocated block before returned</param> + /// <returns>The unmanaged <see cref="MemoryHandle{T}"/></returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged + { + ArgumentNullException.ThrowIfNull(heap); + ArgumentOutOfRangeException.ThrowIfNegative(elements); + + if (elements == 0) + { + //Return an empty handle + return default; + } + + //If zero flag is set then specify zeroing memory (safe case because of the above check) + IntPtr block = heap.Alloc((nuint)elements, (nuint)sizeof(T), zero); + + return new(heap, block, elements); + } + + /// <summary> + /// Rents a new array and stores it as a resource within an <see cref="OpenResourceHandle{T}"/> to return the + /// array when work is completed + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="pool"></param> + /// <param name="size">The minimum size array to allocate</param> + /// <param name="zero">Should elements from 0 to size be set to default(T)</param> + /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns> + public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged + { + ArgumentNullException.ThrowIfNull(pool); + + if (size <= 0) + { + return default; + } + + T[] array = pool.Rent(size); + + if (zero) + { + InitializeBlock(array, (uint)size); + } + + return new(pool, array, size); + } + /// <summary> /// Allocates a block of unmanaged, or pooled manaaged memory depending on /// compilation flags and runtime unamanged allocators. @@ -43,40 +112,20 @@ namespace VNLib.Utils.Memory /// <param name="elements">The number of elements of the type within the block</param> /// <param name="zero">Flag to zero elements during allocation before the method returns</param> /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="OutOfMemoryException"></exception> public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(int elements, bool zero = false) where T : unmanaged { - if (elements < 0) - { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); - } + ArgumentOutOfRangeException.ThrowIfNegative(elements); if (elements == 0) { return default; } - /* - * We may allocate from the share heap only if the heap is not using locks - * or if the element size could cause performance issues because its too large - * to use a managed array. - * - * We want to avoid allocations, that may end up in the LOH if we can - */ - - if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || ByteCount<T>((uint)elements) > MAX_UNSAFE_POOL_SIZE) - { - // Alloc from heap - IntPtr block = Shared.Alloc((uint)elements, (uint)sizeof(T), zero); - //Init new handle - return new(Shared, block, elements); - } - else - { - //Rent the array from the pool - return ArrayPool<T>.Shared.UnsafeAlloc(elements, zero); - } + return UseUnmanagedHeap<T>(Shared, (uint)elements) + ? UnsafeAlloc<T>(Shared, elements, zero) + : UnsafeAlloc(ArrayPool<T>.Shared, elements, zero); } /// <summary> @@ -88,18 +137,81 @@ namespace VNLib.Utils.Memory /// <param name="elements">The number of elements of the type within the block</param> /// <param name="zero">Flag to zero elements during allocation before the method returns</param> /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="OutOfMemoryException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UnsafeMemoryHandle<T> UnsafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged { - if (elements < 0) + ArgumentOutOfRangeException.ThrowIfNegative(elements); + return UnsafeAlloc<T>(elements: (int)NearestPage<T>(elements), zero); + } + + /// <summary> + /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type + /// </summary> + /// <typeparam name="T">Unmanaged data type to create a block of</typeparam> + /// <param name="heap"></param> + /// <param name="elements">The size of the block (number of elements)</param> + /// <param name="zero">A flag that zeros the allocated block before returned</param> + /// <returns>The unmanaged <see cref="MemoryHandle{T}"/></returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + public static MemoryHandle<T> SafeAlloc<T>(IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged + { + ArgumentNullException.ThrowIfNull(heap); + + //Return empty handle if no elements were specified + if (elements == 0) { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); + return new MemoryHandle<T>(); } + + IntPtr block = heap.Alloc(elements, (nuint)sizeof(T), zero); - //Round to nearest page (in bytes) - nint np = NearestPage<T>(elements); - return UnsafeAlloc<T>((int)np, zero); + return new MemoryHandle<T>(heap, block, elements, zero); + } + + /// <summary> + /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type + /// </summary> + /// <typeparam name="T">Unmanaged data type to create a block of</typeparam> + /// <param name="heap"></param> + /// <param name="elements">The size of the block (number of elements)</param> + /// <param name="zero">A flag that zeros the allocated block before returned</param> + /// <returns>The unmanaged <see cref="MemoryHandle{T}"/></returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryHandle<T> SafeAlloc<T>(IUnmangedHeap heap, nint elements, bool zero = false) where T : unmanaged + { + ArgumentOutOfRangeException.ThrowIfNegative(elements); + return SafeAlloc<T>(heap, (nuint)elements, zero); + } + + /// <summary> + /// Rents a new array and stores it as a resource within an <see cref="OpenResourceHandle{T}"/> to return the + /// array when work is completed + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="pool"></param> + /// <param name="size">The minimum size array to allocate</param> + /// <param name="zero">Should elements from 0 to size be set to default(T)</param> + /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns> + public static ArrayPoolBuffer<T> SafeAlloc<T>(ArrayPool<T> pool, int size, bool zero = false) where T : struct + { + ArgumentNullException.ThrowIfNull(pool); + + T[] array = pool.Rent(size); + + if (zero) + { + InitializeBlock(array, (uint)size); + } + + //Use the array pool buffer wrapper to return the array to the pool when the handle is disposed + return new ArrayPoolBuffer<T>(pool, array, size); } /// <summary> @@ -110,35 +222,60 @@ namespace VNLib.Utils.Memory /// <param name="elements">The number of elements of the type within the block</param> /// <param name="zero">Flag to zero elements during allocation before the method returns</param> /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="OutOfMemoryException"></exception> - public static IMemoryHandle<T> SafeAlloc<T>(int elements, bool zero = false) where T : unmanaged + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IMemoryHandle<T> SafeAlloc<T>(nuint elements, bool zero = false) where T : unmanaged { - if (elements < 0) - { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); - } - - /* - * We may allocate from the share heap only if the heap is not using locks - * or if the element size could cause performance issues because its too large - * to use a managed array. - * - * We want to avoid allocations, that may end up in the LOH if we can - */ + ArgumentOutOfRangeException.ThrowIfNegative(elements); - if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || ByteCount<T>((uint)elements) > MAX_UNSAFE_POOL_SIZE) + if (UseUnmanagedHeap<T>(Shared, elements)) { - return Shared.Alloc<T>(elements, zero); + return SafeAlloc<T>(Shared, elements, zero); } else { - return new ArrayPoolBuffer<T>(ArrayPool<T>.Shared, elements, zero); + //Should never happen because max pool size guards against this + Debug.Assert(elements <= int.MaxValue); + + return SafeAlloc(ArrayPool<T>.Shared, (int)elements, zero); } } /// <summary> /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators. + /// </summary> + /// <typeparam name="T">The unamanged type to allocate</typeparam> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IMemoryHandle<T> SafeAlloc<T>(int elements, bool zero = false) where T : unmanaged + { + ArgumentOutOfRangeException.ThrowIfNegative(elements); + return SafeAlloc<T>((nuint)elements, zero); + } + + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators, rounded up to the + /// neareset memory page. + /// </summary> + /// <typeparam name="T">The unamanged type to allocate</typeparam> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IMemoryHandle<T> SafeAllocNearestPage<T>(nuint elements, bool zero = false) where T : unmanaged + => SafeAlloc<T>(elements: NearestPage<T>(elements), zero); + + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on /// compilation flags and runtime unamanged allocators, rounded up to the /// neareset memory page. /// </summary> @@ -146,21 +283,52 @@ namespace VNLib.Utils.Memory /// <param name="elements">The number of elements of the type within the block</param> /// <param name="zero">Flag to zero elements during allocation before the method returns</param> /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="OutOfMemoryException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IMemoryHandle<T> SafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged { - if (elements < 0) - { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); - } + ArgumentOutOfRangeException.ThrowIfNegative(elements); + return SafeAllocNearestPage<T>((nuint)elements, zero); + } - //Round to nearest page (in bytes) - nint np = NearestPage<T>(elements); - return SafeAlloc<T>((int)np, zero); + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators, rounded up to the + /// neareset memory page. + /// </summary> + /// <typeparam name="T">The unamanged type to allocate</typeparam> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <param name="heap">The heap to allocate the block of memory from</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryHandle<T> SafeAllocNearestPage<T>(IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged + { + ArgumentOutOfRangeException.ThrowIfNegative(elements); + + return SafeAllocNearestPage<T>(heap, (nuint)elements, zero); } /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators, rounded up to the + /// neareset memory page. + /// </summary> + /// <typeparam name="T">The unamanged type to allocate</typeparam> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <param name="heap">The heap to allocate the block of memory from</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryHandle<T> SafeAllocNearestPage<T>(IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged + => SafeAlloc<T>(heap, elements: NearestPage<T>(elements), zero); + + /// <summary> /// Allocates a structure of the specified type on the specified /// unmanged heap and optionally zero's it's memory /// </summary> @@ -191,13 +359,8 @@ namespace VNLib.Utils.Memory /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ObjectDisposedException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T StructAllocRef<T>(IUnmangedHeap heap, bool zero) where T : unmanaged - { - //Alloc structure - T* ptr = StructAlloc<T>(heap, zero); - //Get a reference and assign it - return ref Unsafe.AsRef<T>(ptr); - } + public static ref T StructAllocRef<T>(IUnmangedHeap heap, bool zero) where T : unmanaged + => ref Unsafe.AsRef<T>(StructAlloc<T>(heap, zero)); /// <summary> /// Frees a structure allocated with <see cref="StructAlloc{T}(IUnmangedHeap, bool)"/> @@ -207,7 +370,8 @@ namespace VNLib.Utils.Memory /// <param name="structPtr">A pointer to the unmanaged structure to free</param> /// <exception cref="ArgumentNullException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void StructFree<T>(IUnmangedHeap heap, T* structPtr) where T : unmanaged => StructFree(heap, (void*)structPtr); + public static void StructFree<T>(IUnmangedHeap heap, T* structPtr) where T : unmanaged + => StructFree(heap, (void*)structPtr); /// <summary> /// Frees a structure allocated with <see cref="StructAllocRef{T}(IUnmangedHeap, bool)"/> @@ -218,7 +382,8 @@ namespace VNLib.Utils.Memory /// <param name="structRef">A reference to the unmanaged structure to free</param> /// <exception cref="ArgumentNullException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void StructFreeRef<T>(IUnmangedHeap heap, ref T structRef) where T : unmanaged => StructFree(heap, Unsafe.AsPointer(ref structRef)); + public static void StructFreeRef<T>(IUnmangedHeap heap, ref T structRef) where T : unmanaged + => StructFree(heap, Unsafe.AsPointer(ref structRef)); /// <summary> /// Frees a structure allocated with <see cref="StructAlloc{T}(IUnmangedHeap, bool)"/> @@ -233,7 +398,7 @@ namespace VNLib.Utils.Memory //Get intpointer IntPtr ptr = (IntPtr)structPtr; - //Free + bool isFree = heap.Free(ref ptr); Debug.Assert(isFree, $"Structure free failed for heap {heap.GetHashCode()}, struct address {ptr:x}"); } @@ -249,39 +414,20 @@ namespace VNLib.Utils.Memory /// <param name="elements">The number of elements of the type within the block</param> /// <param name="zero">Flag to zero elements during allocation before the method returns</param> /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="OutOfMemoryException"></exception> public static UnsafeMemoryHandle<byte> UnsafeAlloc(int elements, bool zero = false) { - if (elements < 0) - { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); - } + ArgumentOutOfRangeException.ThrowIfNegative(elements); if(elements == 0) { return default; } - /* - * We may allocate from the share heap only if the heap is not using locks - * or if the element size could cause performance issues because its too large - * to use a managed array. - * - * We want to avoid allocations, that may end up in the LOH if we can - */ - - if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || elements > MAX_UNSAFE_POOL_SIZE) - { - // Alloc from heap - IntPtr block = Shared.Alloc((uint)elements, 1, zero); - //Init new handle - return new(Shared, block, elements); - } - else - { - return ArrayPool<byte>.Shared.UnsafeAlloc(elements, zero); - } + return UseUnmanagedHeap<byte>(Shared, (uint)elements) + ? UnsafeAlloc<byte>(Shared, elements, zero) + : UnsafeAlloc(ArrayPool<byte>.Shared, elements, zero); } /// <summary> @@ -292,19 +438,15 @@ namespace VNLib.Utils.Memory /// <param name="elements">The number of elements of the type within the block</param> /// <param name="zero">Flag to zero elements during allocation before the method returns</param> /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="OutOfMemoryException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UnsafeMemoryHandle<byte> UnsafeAllocNearestPage(int elements, bool zero = false) { - if (elements < 0) - { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); - } + ArgumentOutOfRangeException.ThrowIfNegative(elements); //Round to nearest page (in bytes) - nint np = NearestPage(elements); - return UnsafeAlloc((int)np, zero); + return UnsafeAlloc(elements: (int)NearestPage(elements), zero); } /// <summary> @@ -314,31 +456,15 @@ namespace VNLib.Utils.Memory /// <param name="elements">The number of elements of the type within the block</param> /// <param name="zero">Flag to zero elements during allocation before the method returns</param> /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="OutOfMemoryException"></exception> public static IMemoryHandle<byte> SafeAlloc(int elements, bool zero = false) { - if (elements < 0) - { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); - } - - /* - * We may allocate from the share heap only if the heap is not using locks - * or if the element size could cause performance issues because its too large - * to use a managed array. - * - * We want to avoid allocations, that may end up in the LOH if we can - */ + ArgumentOutOfRangeException.ThrowIfNegative(elements); - if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || elements > MAX_UNSAFE_POOL_SIZE) - { - return Shared.Alloc<byte>(elements, zero); - } - else - { - return new ArrayPoolBuffer<byte>(ArrayPool<byte>.Shared, elements, zero); - } + return UseUnmanagedHeap<byte>(Shared, (uint)elements) + ? SafeAlloc<byte>(Shared, (nuint)elements, zero) + : SafeAlloc(ArrayPool<byte>.Shared, elements, zero); } /// <summary> @@ -349,18 +475,12 @@ namespace VNLib.Utils.Memory /// <param name="elements">The number of elements of the type within the block</param> /// <param name="zero">Flag to zero elements during allocation before the method returns</param> /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> /// <exception cref="OutOfMemoryException"></exception> public static IMemoryHandle<byte> SafeAllocNearestPage(int elements, bool zero = false) { - if (elements < 0) - { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); - } - - //Round to nearest page (in bytes) - nint np = NearestPage(elements); - return SafeAlloc((int)np, zero); + ArgumentOutOfRangeException.ThrowIfNegative(elements); + return SafeAlloc(elements: (int)NearestPage(elements), zero); } #endregion diff --git a/lib/Utils/src/Memory/UnmanagedHeapBase.cs b/lib/Utils/src/Memory/UnmanagedHeapBase.cs index 7f42761..a9730b7 100644 --- a/lib/Utils/src/Memory/UnmanagedHeapBase.cs +++ b/lib/Utils/src/Memory/UnmanagedHeapBase.cs @@ -23,6 +23,7 @@ */ using System; +using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; @@ -111,25 +112,32 @@ namespace VNLib.Utils.Memory bool result; //If disposed, set the block handle to zero and exit to avoid raising exceptions during finalization - if (IsClosed || IsInvalid) + if (IsClosed) { block = LPVOID.Zero; return true; } + /* + * Checking for invalid is not really necesasry because + * the only way the handle can be invalidated is + * if some derrived class mutates the handle value + * and doesn't close the handle + */ + Debug.Assert(IsInvalid == false); + if ((flags & HeapCreation.UseSynchronization) > 0) { //wait for lock lock (HeapLock) - { - //Free block + { result = FreeBlock(block); //Release lock before releasing handle } } else { - //No lock + //No lock needed result = FreeBlock(block); } diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs index 63342b5..68bc35c 100644 --- a/lib/Utils/tests/Memory/MemoryUtilTests.cs +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -202,7 +202,7 @@ namespace VNLib.Utils.Memory.Tests { } //test against negative number - Assert.ThrowsException<ArgumentException>(() => MemoryUtil.UnsafeAlloc<byte>(-1)); + Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.UnsafeAlloc<byte>(-1)); //Alloc large block test (100mb) const int largTestSize = 100000 * 1024; @@ -257,7 +257,7 @@ namespace VNLib.Utils.Memory.Tests } //Negative value - Assert.ThrowsException<ArgumentException>(() => _ = MemoryUtil.UnsafeAlloc<byte>(-1)); + Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = MemoryUtil.UnsafeAlloc<byte>(-1)); /* @@ -315,7 +315,7 @@ namespace VNLib.Utils.Memory.Tests } //Negative value - Assert.ThrowsException<ArgumentException>(() => _ = MemoryUtil.SafeAlloc<byte>(-1)); + Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = MemoryUtil.SafeAlloc<byte>(-1)); /* * Alloc random sized blocks in a loop, confirm they are empty |