aboutsummaryrefslogtreecommitdiff
path: root/lib/Utils
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-06-16 01:12:07 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-06-16 01:12:07 -0400
commit07ddf6738d32127926d07b1366e56d2a2308b53b (patch)
tree02f01a1a15db95fa082a29a0e9d18f62a016579d /lib/Utils
parentff15c05a9c3e632c39f3889820fb7d889342b452 (diff)
perf: Absolutely yuge perf boosts
Diffstat (limited to 'lib/Utils')
-rw-r--r--lib/Utils/src/Extensions/MemoryExtensions.cs102
-rw-r--r--lib/Utils/src/IO/VnMemoryStream.cs4
-rw-r--r--lib/Utils/src/Memory/MemoryUtilAlloc.cs378
-rw-r--r--lib/Utils/src/Memory/UnmanagedHeapBase.cs16
-rw-r--r--lib/Utils/tests/Memory/MemoryUtilTests.cs6
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