Enhance noise generation and memory management

Added new noise generation methods in `ParallelNoiseBenchmark`, including `Frac`, `Lerp`, and `GradientNoiseDirect`, and updated the `GradientNoise` method to utilize them. Changed constants to use `_LENGTH` for consistency.

Changed `Arena` and `DynamicArena` classes to use `uint` instead of `ulong` for size fields, improving memory usage. Updated memory allocation to use `NativeMemory` for better performance and safety.

Updated `UnsafeArray<T>` and `UnsafeList<T>` classes to replace `Marshal` methods with `NativeMemory`, enhancing performance and safety. Modified `Dispose` methods to use `NativeMemory.AlignedFree`.

Added `MemoryUtilities` class with new methods for memory management, including `MemClear`, `MemSet`, `MemCpy`, `SizeOf`, and `AlignOf`, utilizing `NativeMemory`.

Fixed minor cleanup in the `ObjectPool` class's `Dispose` method.
This commit is contained in:
2025-03-25 09:49:49 +09:00
parent aa1e9e6b1d
commit 7bcd699eb9
7 changed files with 120 additions and 73 deletions

View File

@@ -14,18 +14,6 @@ public class ParallelNoiseBenchmark
public UnsafeArray<float> buffers; public UnsafeArray<float> buffers;
public int width; public int width;
public int height; public int height;
public void Execute(int index)
{
var x = index % width;
var y = index / height;
var uv = new Vector2(x, y);
buffers[index] = GradientNoise(uv);
}
}
private const int _WIDTH = 512;
private const int _HEIGHT = 512;
private const int _LENGTH = _WIDTH * _HEIGHT;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float Frac(float x) private static float Frac(float x)
@@ -49,7 +37,7 @@ public class ParallelNoiseBenchmark
return Vector2.Normalize(new Vector2(x - MathF.Floor(x + 0.5f), MathF.Abs(x) - 0.5f)); return Vector2.Normalize(new Vector2(x - MathF.Floor(x + 0.5f), MathF.Abs(x) - 0.5f));
} }
private static float GradientNoise(Vector2 uv) public static float GradientNoise(Vector2 uv)
{ {
var ip = new Vector2(MathF.Floor(uv.X), MathF.Floor(uv.Y)); var ip = new Vector2(MathF.Floor(uv.X), MathF.Floor(uv.Y));
var fp = new Vector2(Frac(uv.X), Frac(uv.Y)); var fp = new Vector2(Frac(uv.X), Frac(uv.Y));
@@ -63,10 +51,23 @@ public class ParallelNoiseBenchmark
return Lerp(Lerp(d00, d10, fp.Y), Lerp(d01, d11, fp.Y), fp.X); return Lerp(Lerp(d00, d10, fp.Y), Lerp(d01, d11, fp.Y), fp.X);
} }
public void Execute(int index)
{
var x = index % width;
var y = index / height;
var uv = new Vector2(x, y);
buffers[index] = GradientNoise(uv);
}
}
private const int _WIDTH = 512;
private const int _HEIGHT = 512;
private const int _LENGTH = _WIDTH * _HEIGHT;
[Benchmark] [Benchmark]
public void JobSystem() public void JobSystem()
{ {
using var buffers = new UnsafeArray<float>(_WIDTH * _HEIGHT, AllocationType.UnInitialized); using var buffers = new UnsafeArray<float>(_LENGTH, AllocationType.UnInitialized);
var job = new NoiseJob() var job = new NoiseJob()
{ {
buffers = buffers, buffers = buffers,
@@ -81,14 +82,14 @@ public class ParallelNoiseBenchmark
[Benchmark] [Benchmark]
public void ParallelFor() public void ParallelFor()
{ {
using var buffers = new UnsafeArray<float>(_WIDTH * _HEIGHT, AllocationType.UnInitialized); using var buffers = new UnsafeArray<float>(_LENGTH, AllocationType.UnInitialized);
Parallel.For(0, _LENGTH, i => Parallel.For(0, _LENGTH, i =>
{ {
var x = i % _WIDTH; var x = i % _WIDTH;
var y = i / _HEIGHT; var y = i / _HEIGHT;
var uv = new Vector2(x, y); var uv = new Vector2(x, y);
buffers[i] = GradientNoise(uv); buffers[i] = NoiseJob.GradientNoise(uv);
}); });
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Runtime.InteropServices; using Misaki.HighPerformance.Unsafe.Collections;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Unsafe.Buffer; namespace Misaki.HighPerformance.Unsafe.Buffer;
@@ -8,14 +9,14 @@ namespace Misaki.HighPerformance.Unsafe.Buffer;
public unsafe struct Arena : IDisposable public unsafe struct Arena : IDisposable
{ {
private void* _buffer; private void* _buffer;
private ulong _size; private uint _size;
private ulong _offset; private uint _offset;
private bool _disposed; private bool _disposed;
public Arena(ulong size) public Arena(uint size)
{ {
_buffer = Marshal.AllocHGlobal((IntPtr)size).ToPointer(); _buffer = NativeMemory.Alloc(size);
_size = size; _size = size;
_offset = 0; _offset = 0;
} }
@@ -23,12 +24,13 @@ public unsafe struct Arena : IDisposable
/// <summary> /// <summary>
/// Allocates a block of memory of a specified size with a given alignment. Returns a pointer to the allocated /// Allocates a block of memory of a specified size with a given alignment. Returns a pointer to the allocated
/// memory or null if allocation fails. /// memory or null if allocation fails.
/// Must use <see cref="NativeMemory.AlignedFree"/> to free the memory.
/// </summary> /// </summary>
/// <param name="size">Specifies the amount of memory to allocate in bytes.</param> /// <param name="size">Specifies the amount of memory to allocate in bytes.</param>
/// <param name="alignSize">Defines the alignment requirement for the allocated memory.</param> /// <param name="alignSize">Defines the alignment requirement for the allocated memory.</param>
/// <returns>A pointer to the allocated memory block or null if the allocation cannot be fulfilled.</returns> /// <returns>A pointer to the allocated memory block or null if the allocation cannot be fulfilled.</returns>
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception> /// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
public void* Allocate(ulong size, uint alignSize) public void* Allocate(uint size, uint alignSize, AllocationType allocationType)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
@@ -40,7 +42,11 @@ public unsafe struct Arena : IDisposable
_offset = offset + size; _offset = offset + size;
var ptr = (byte*)_buffer + offset; var ptr = (byte*)_buffer + offset;
MemClear(ptr, (uint)size);
if (allocationType == AllocationType.Clear)
{
MemClear(ptr, size);
}
return ptr; return ptr;
} }
@@ -56,7 +62,7 @@ public unsafe struct Arena : IDisposable
if (clear) if (clear)
{ {
MemClear(_buffer, (uint)_size); MemClear(_buffer, _size);
} }
_offset = 0; _offset = 0;
@@ -64,7 +70,7 @@ public unsafe struct Arena : IDisposable
public void Dispose() public void Dispose()
{ {
Marshal.FreeHGlobal((IntPtr)_buffer); NativeMemory.Free(_buffer);
_buffer = null; _buffer = null;
_size = 0; _size = 0;

View File

@@ -1,4 +1,5 @@
using System.Runtime.InteropServices; using Misaki.HighPerformance.Unsafe.Collections;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Unsafe.Buffer; namespace Misaki.HighPerformance.Unsafe.Buffer;
@@ -16,28 +17,28 @@ public unsafe struct DynamicArena : IDisposable
private ArenaNode* _root; private ArenaNode* _root;
private ArenaNode* _current; private ArenaNode* _current;
private readonly ulong _initialSize; private readonly uint _initialSize;
private bool _disposed; private bool _disposed;
/// <summary> /// <summary>
/// Initializes a new instance of DynamicArena with the specified initial size. /// Initializes a new instance of DynamicArena with the specified initial size.
/// </summary> /// </summary>
/// <param name="initialSize">The initial size in bytes for the first arena block.</param> /// <param name="initialSize">The initial size in bytes for the first arena block.</param>
public DynamicArena(ulong initialSize) public DynamicArena(uint initialSize)
{ {
_initialSize = initialSize; _initialSize = initialSize;
_root = (ArenaNode*)Marshal.AllocHGlobal(sizeof(ArenaNode)); _root = (ArenaNode*)NativeMemory.Alloc(SizeOf<ArenaNode>());
_root->arena = new Arena(initialSize); _root->arena = new Arena(initialSize);
_root->next = null; _root->next = null;
_current = _root; _current = _root;
_disposed = false; _disposed = false;
} }
private bool CreateNewNode(ulong size) private bool CreateNewNode(uint size)
{ {
try try
{ {
var newNode = (ArenaNode*)Marshal.AllocHGlobal(sizeof(ArenaNode)); var newNode = (ArenaNode*)NativeMemory.Alloc(SizeOf<ArenaNode>());
newNode->arena = new Arena(size); newNode->arena = new Arena(size);
newNode->next = null; newNode->next = null;
@@ -58,7 +59,7 @@ public unsafe struct DynamicArena : IDisposable
/// <param name="alignSize">Alignment requirement for the memory block.</param> /// <param name="alignSize">Alignment requirement for the memory block.</param>
/// <returns>Pointer to the allocated memory block.</returns> /// <returns>Pointer to the allocated memory block.</returns>
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception> /// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
public void* Allocate(ulong size, uint alignSize) public void* Allocate(uint size, uint alignSize, AllocationType allocationType)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
@@ -67,7 +68,7 @@ public unsafe struct DynamicArena : IDisposable
while (current != null) while (current != null)
{ {
result = current->arena.Allocate(size, alignSize); result = current->arena.Allocate(size, alignSize, allocationType);
if (result != null) if (result != null)
return result; return result;
@@ -113,7 +114,7 @@ public unsafe struct DynamicArena : IDisposable
{ {
var next = current->next; var next = current->next;
current->arena.Dispose(); current->arena.Dispose();
Marshal.FreeHGlobal((IntPtr)current); NativeMemory.Free(current);
current = next; current = next;
} }

View File

@@ -78,7 +78,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>, IEnumerable<T> where
public UnsafeArray(int size, AllocationType allocationType) public UnsafeArray(int size, AllocationType allocationType)
{ {
_size = size; _size = size;
_buffer = (T*)Marshal.AllocHGlobal(size * sizeof(T)).ToPointer(); _buffer = (T*)NativeMemory.AlignedAlloc((nuint)(size * sizeof(T)), (nuint)AlignOf<T>());
if (allocationType == AllocationType.Clear) if (allocationType == AllocationType.Clear)
{ {
@@ -93,7 +93,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>, IEnumerable<T> where
return; return;
} }
_buffer = (T*)Marshal.ReAllocHGlobal((IntPtr)_buffer, newSize * sizeof(T)).ToPointer(); _buffer = (T*)NativeMemory.AlignedRealloc(_buffer, (nuint)(newSize * sizeof(T)), (nuint)AlignOf<T>());
_size = newSize; _size = newSize;
} }
@@ -105,7 +105,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>, IEnumerable<T> where
public void Dispose() public void Dispose()
{ {
Marshal.FreeHGlobal((IntPtr)_buffer); NativeMemory.AlignedFree(_buffer);
_buffer = null; _buffer = null;
_size = 0; _size = 0;

View File

@@ -124,7 +124,7 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>, IEnumerable<T> where
public UnsafeList(int capacity, AllocationType allocationType) public UnsafeList(int capacity, AllocationType allocationType)
{ {
_buffer = (T*)Marshal.AllocHGlobal(capacity * sizeof(T)); _buffer = (T*)NativeMemory.AlignedAlloc((nuint)(capacity * sizeof(T)), (nuint)AlignOf<T>());
_size = 0; _size = 0;
_capacity = capacity; _capacity = capacity;
@@ -270,7 +270,7 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>, IEnumerable<T> where
return; return;
} }
_buffer = (T*)Marshal.ReAllocHGlobal((IntPtr)_buffer, newSize * sizeof(T)).ToPointer(); _buffer = (T*)NativeMemory.AlignedRealloc(_buffer, (nuint)(newSize * sizeof(T)), (nuint)AlignOf<T>());
_size = newSize; _size = newSize;
} }
@@ -282,7 +282,7 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>, IEnumerable<T> where
public void Dispose() public void Dispose()
{ {
Marshal.FreeHGlobal((IntPtr)_buffer); NativeMemory.AlignedFree(_buffer);
_buffer = null; _buffer = null;
_size = 0; _size = 0;

View File

@@ -1,8 +1,16 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Unsafe.Helpers; namespace Misaki.HighPerformance.Unsafe.Helpers;
public unsafe static class MemoryUtilities public unsafe static class MemoryUtilities
{ {
[StructLayout(LayoutKind.Sequential)]
private struct AlignOfHelper<T> where T : struct
{
public byte dummy;
public T data;
}
/// <summary> /// <summary>
/// Clears a block of memory by setting it to zero. It initializes a specified number of bytes at a given memory /// Clears a block of memory by setting it to zero. It initializes a specified number of bytes at a given memory
/// address. /// address.
@@ -10,32 +18,63 @@ public unsafe static class MemoryUtilities
/// <param name="ptr">Specifies the memory address where the clearing operation will begin.</param> /// <param name="ptr">Specifies the memory address where the clearing operation will begin.</param>
/// <param name="size">Indicates the number of bytes to be cleared in the memory block.</param> /// <param name="size">Indicates the number of bytes to be cleared in the memory block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemClear(void* ptr, uint size) public static void MemClear(void* ptr, nuint size)
{ {
SystemUnsfae.InitBlock(ptr, 0, size); NativeMemory.Clear(ptr, size);
} }
/// <summary> /// <summary>
/// Sets a block of memory to a specified byte value for a given size. /// Sets a block of memory to a specified byte value for a given size.
/// </summary> /// </summary>
/// <param name="ptr">The memory address where the byte value will be set.</param> /// <param name="ptr">The memory address where the byte value will be set.</param>
/// <param name="value">The byte value to which the memory block will be initialized.</param>
/// <param name="size">The number of bytes to set to the specified value.</param> /// <param name="size">The number of bytes to set to the specified value.</param>
/// <param name="value">The byte value to which the memory block will be initialized.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemSet(void* ptr, byte value, uint size) public static void MemSet(void* ptr, nuint size, byte value)
{ {
SystemUnsfae.InitBlock(ptr, value, size); NativeMemory.Fill(ptr, size, value);
} }
/// <summary> /// <summary>
/// Copies a block of memory from a source location to a destination location. /// Copies a block of memory from a source location to a destination location.
/// </summary> /// </summary>
/// <param name="destination">Specifies the memory address where the copied data will be stored.</param>
/// <param name="source">Indicates the memory address from which data will be copied.</param> /// <param name="source">Indicates the memory address from which data will be copied.</param>
/// <param name="destination">Specifies the memory address where the copied data will be stored.</param>
/// <param name="size">Defines the number of bytes to be copied from the source to the destination.</param> /// <param name="size">Defines the number of bytes to be copied from the source to the destination.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemCpy(void* destination, void* source, uint size) public static void MemCpy(void* source, void* destination, nuint size)
{ {
SystemUnsfae.CopyBlock(destination, source, size); NativeMemory.Copy(source, destination, size);
}
/// <summary>
/// Calculates the size in bytes of a specified unmanaged type.
/// </summary>
/// <typeparam name="T">Represents an unmanaged type for which the size is being calculated.</typeparam>
/// <returns>Returns the size of the specified type as an unsigned integer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nuint SizeOf<T>() where T : unmanaged
{
return (nuint)sizeof(T);
}
/// <summary>
/// Calculates the alignment size of a specified unmanaged type.
/// </summary>
/// <typeparam name="T">Represents an unmanaged type for which the alignment size is being calculated.</typeparam>
/// <returns>Returns the difference in size between a helper structure and the specified type.</returns>
public static int AlignOf<T>() where T : unmanaged
{
return sizeof(AlignOfHelper<T>) - sizeof(T);
}
/// <summary>
/// Calculates the alignment size difference between a specified struct and a helper struct.
/// </summary>
/// <typeparam name="T">Represents a value type that is used to determine the alignment size.</typeparam>
/// <returns>Returns the size difference in bytes as an integer.</returns>
public static int MarshalAlignOf<T>() where T : struct
{
return Marshal.SizeOf<AlignOfHelper<T>>() - Marshal.SizeOf<T>();
} }
} }

View File

@@ -104,7 +104,7 @@ namespace Misaki.HighPerformance.Buffer
{ {
PoolCleanup(); PoolCleanup();
_disposed = true: _disposed = true;
} }
} }
} }