diff --git a/Misaki.HighPerformance.Test/CollectionBenchmark.cs b/Misaki.HighPerformance.Test/CollectionBenchmark.cs index 4169fee..9f2f76b 100644 --- a/Misaki.HighPerformance.Test/CollectionBenchmark.cs +++ b/Misaki.HighPerformance.Test/CollectionBenchmark.cs @@ -1,6 +1,6 @@ using BenchmarkDotNet.Attributes; using Misaki.HighPerformance.Unsafe.Collections; -using Misaki.HighPerformance.Unsafe.Collections.Services; +using Misaki.HighPerformance.Unsafe.Services; namespace Misaki.HighPerformance.Test; diff --git a/Misaki.HighPerformance.Test/ParallelNoiseBenchmark.cs b/Misaki.HighPerformance.Test/ParallelNoiseBenchmark.cs index 3aa8d73..395500f 100644 --- a/Misaki.HighPerformance.Test/ParallelNoiseBenchmark.cs +++ b/Misaki.HighPerformance.Test/ParallelNoiseBenchmark.cs @@ -61,7 +61,7 @@ public class ParallelNoiseBenchmark [Benchmark] public static void JobSystem() { - using var buffers = new UnsafeArray(_LENGTH, Allocator.Persistent, AllocationType.UnInitialized); + using var buffers = new UnsafeArray(_LENGTH, Allocator.Persistent, AllocationOption.UnInitialized); var job = new NoiseJob() { buffers = buffers, @@ -76,7 +76,7 @@ public class ParallelNoiseBenchmark [Benchmark] public static void ParallelFor() { - using var buffers = new UnsafeArray(_LENGTH, Allocator.Persistent, AllocationType.UnInitialized); + using var buffers = new UnsafeArray(_LENGTH, Allocator.Persistent, AllocationOption.UnInitialized); Parallel.For(0, _LENGTH, i => { @@ -90,7 +90,7 @@ public class ParallelNoiseBenchmark [Benchmark] public static void For() { - using var buffers = new UnsafeArray(_LENGTH, Allocator.Persistent, AllocationType.UnInitialized); + using var buffers = new UnsafeArray(_LENGTH, Allocator.Persistent, AllocationOption.UnInitialized); for (var i = 0; i < _LENGTH; i++) { var x = i % _WIDTH; diff --git a/Misaki.HighPerformance.Test/Program.cs b/Misaki.HighPerformance.Test/Program.cs index 0b7b2e1..c8e4300 100644 --- a/Misaki.HighPerformance.Test/Program.cs +++ b/Misaki.HighPerformance.Test/Program.cs @@ -1,5 +1,5 @@ using Misaki.HighPerformance.Test; -using Misaki.HighPerformance.Unsafe.Collections.Services; +using Misaki.HighPerformance.Unsafe.Services; AllocationManager.Initialize(512_000); var test = new CollectionBenchmark(); diff --git a/Misaki.HighPerformance.Unsafe/Buffer/Arena .cs b/Misaki.HighPerformance.Unsafe/Buffer/Arena .cs index e06de3d..92e65c9 100644 --- a/Misaki.HighPerformance.Unsafe/Buffer/Arena .cs +++ b/Misaki.HighPerformance.Unsafe/Buffer/Arena .cs @@ -30,7 +30,7 @@ public unsafe struct Arena : IDisposable /// Defines the alignment requirement for the allocated memory. /// A pointer to the allocated memory block or null if the allocation cannot be fulfilled. /// Thrown if the arena has been disposed. - public void* Allocate(uint size, uint alignSize, AllocationType allocationType) + public void* Allocate(uint size, uint alignSize, AllocationOption allocationType) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -41,9 +41,9 @@ public unsafe struct Arena : IDisposable } _offset = offset + size; - var ptr = (byte*)_buffer + offset; + var ptr = _buffer + offset; - if (allocationType == AllocationType.Clear) + if (allocationType == AllocationOption.Clear) { MemClear(ptr, size); } diff --git a/Misaki.HighPerformance.Unsafe/Buffer/DynamicArena.cs b/Misaki.HighPerformance.Unsafe/Buffer/DynamicArena.cs index e276490..e47e76f 100644 --- a/Misaki.HighPerformance.Unsafe/Buffer/DynamicArena.cs +++ b/Misaki.HighPerformance.Unsafe/Buffer/DynamicArena.cs @@ -57,7 +57,7 @@ public unsafe struct DynamicArena : IDisposable /// Alignment requirement for the memory block. /// Pointer to the allocated memory block. /// Thrown if the arena has been disposed. - public void* Allocate(uint size, uint alignSize, AllocationType allocationType) + public void* Allocate(uint size, uint alignSize, AllocationOption allocationType) { ObjectDisposedException.ThrowIf(_disposed, this); diff --git a/Misaki.HighPerformance.Unsafe/Collections/AllocationOption.cs b/Misaki.HighPerformance.Unsafe/Collections/AllocationOption.cs new file mode 100644 index 0000000..829ff23 --- /dev/null +++ b/Misaki.HighPerformance.Unsafe/Collections/AllocationOption.cs @@ -0,0 +1,15 @@ +namespace Misaki.HighPerformance.Unsafe.Collections; + +public enum AllocationOption : byte +{ + UnInitialized, + Clear +} + +public enum Allocator: byte +{ + // Make the first allocator as invalid because we don't want to user create a defualt collection without passing any parameters + Invalid = 0, + Temp = 1, + Persistent = 2, +} \ No newline at end of file diff --git a/Misaki.HighPerformance.Unsafe/Collections/AllocationType.cs b/Misaki.HighPerformance.Unsafe/Collections/AllocationType.cs deleted file mode 100644 index eeac6e1..0000000 --- a/Misaki.HighPerformance.Unsafe/Collections/AllocationType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Misaki.HighPerformance.Unsafe.Collections; - -public enum AllocationType -{ - UnInitialized, - Clear -} - -public enum Allocator -{ - Temp, - Persistent -} \ No newline at end of file diff --git a/Misaki.HighPerformance.Unsafe/Collections/UnsafeArray.cs b/Misaki.HighPerformance.Unsafe/Collections/UnsafeArray.cs index 97cf129..9e955bc 100644 --- a/Misaki.HighPerformance.Unsafe/Collections/UnsafeArray.cs +++ b/Misaki.HighPerformance.Unsafe/Collections/UnsafeArray.cs @@ -1,6 +1,6 @@ using Misaki.HighPerformance.Unsafe.Collections.Contracts; -using Misaki.HighPerformance.Unsafe.Collections.Services; using Misaki.HighPerformance.Unsafe.Helpers; +using Misaki.HighPerformance.Unsafe.Services; using System.Collections; using System.Runtime.CompilerServices; @@ -66,6 +66,8 @@ public unsafe struct UnsafeArray : IUnsafeCollection private T* _buffer; private int _count; + private readonly Allocator _allocator; + public readonly int Count => _count; public readonly ref T this[int index] @@ -90,7 +92,7 @@ public unsafe struct UnsafeArray : IUnsafeCollection /// Specifies the number of elements to allocate in the array, which must be greater than zero. /// Determines how the allocated memory should be initialized, either uninitialized or cleared. /// Thrown when the specified number of elements is less than or equal to zero. - public UnsafeArray(int count, Allocator allocator, AllocationType allocationType = AllocationType.UnInitialized) + public UnsafeArray(int count, Allocator allocator, AllocationOption allocationType = AllocationOption.UnInitialized) { if (count <= 0) { @@ -100,7 +102,7 @@ public unsafe struct UnsafeArray : IUnsafeCollection _buffer = AllocationManager.Allocate((uint)count, (uint)AlignOf(), allocator, allocationType); _count = count; - if (allocationType == AllocationType.Clear) + if (allocationType == AllocationOption.Clear) { Clear(); } @@ -132,7 +134,7 @@ public unsafe struct UnsafeArray : IUnsafeCollection [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void Clear() { - MemClear(_buffer, (uint)(_count * sizeof(T))); + MemClear(_buffer, (nuint)(_count * sizeof(T))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -143,7 +145,7 @@ public unsafe struct UnsafeArray : IUnsafeCollection public void Dispose() { - AlignedFree(_buffer); + AllocationManager.Free(_buffer, _allocator); _buffer = null; _count = 0; diff --git a/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashMap.cs b/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashMap.cs index 69cd556..468d6d4 100644 --- a/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashMap.cs +++ b/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashMap.cs @@ -92,9 +92,9 @@ public unsafe struct UnsafeHashMap : IUnsafeCollection> GetEnumerator() => new Enumerator((HashMapHelper*)UnsafeUtilities.AddressOf(ref _hashMap)); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public UnsafeHashMap(int capacity) + public UnsafeHashMap(int capacity, Allocator allocator) { - _hashMap = new HashMapHelper(capacity, sizeof(TValue), HashMapHelper.MINIMAL_CAPACITY); + _hashMap = new HashMapHelper(capacity, sizeof(TValue), HashMapHelper.MINIMAL_CAPACITY, allocator); } /// diff --git a/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashSet.cs b/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashSet.cs index 50c81be..610d7d3 100644 --- a/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashSet.cs +++ b/Misaki.HighPerformance.Unsafe/Collections/UnsafeHashSet.cs @@ -49,9 +49,9 @@ public unsafe struct UnsafeHashSet : IUnsafeCollection, IEnumerable public IEnumerator GetEnumerator() => new Enumerator((HashMapHelper*)UnsafeUtilities.AddressOf(ref _hashMap)); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public UnsafeHashSet(int capacity) + public UnsafeHashSet(int capacity, Allocator allocator) { - _hashMap = new HashMapHelper(capacity, 0, HashMapHelper.MINIMAL_CAPACITY); + _hashMap = new HashMapHelper(capacity, 0, HashMapHelper.MINIMAL_CAPACITY, allocator); } /// diff --git a/Misaki.HighPerformance.Unsafe/Collections/UnsafeList.cs b/Misaki.HighPerformance.Unsafe/Collections/UnsafeList.cs index 92f1fe3..09aa534 100644 --- a/Misaki.HighPerformance.Unsafe/Collections/UnsafeList.cs +++ b/Misaki.HighPerformance.Unsafe/Collections/UnsafeList.cs @@ -129,12 +129,12 @@ public unsafe struct UnsafeList : IUnsafeCollection public ParallelWriter AsParallelWriter() => new((UnsafeList*)UnsafeUtilities.AddressOf(ref this)); - public UnsafeList(int capacity, Allocator allocator, AllocationType allocationType = AllocationType.UnInitialized) + public UnsafeList(int capacity, Allocator allocator, AllocationOption allocationType = AllocationOption.UnInitialized) { _array = new UnsafeArray(capacity, allocator, allocationType); _count = 0; - if (allocationType == AllocationType.Clear) + if (allocationType == AllocationOption.Clear) { Clear(); } diff --git a/Misaki.HighPerformance.Unsafe/Collections/UnsafeQueue.cs b/Misaki.HighPerformance.Unsafe/Collections/UnsafeQueue.cs index d1b2a21..a13ce5d 100644 --- a/Misaki.HighPerformance.Unsafe/Collections/UnsafeQueue.cs +++ b/Misaki.HighPerformance.Unsafe/Collections/UnsafeQueue.cs @@ -80,13 +80,13 @@ public unsafe struct UnsafeQueue : IUnsafeCollection public IEnumerator GetEnumerator() => new Enumerator((UnsafeQueue*)UnsafeUtilities.AddressOf(ref this)); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public UnsafeQueue(int capacity, Allocator allocator, AllocationType allocationType = AllocationType.UnInitialized) + public UnsafeQueue(int capacity, Allocator allocator, AllocationOption allocationType = AllocationOption.UnInitialized) { _array = new UnsafeArray(capacity, allocator, allocationType); _count = 0; _offset = 0; - if (allocationType == AllocationType.Clear) + if (allocationType == AllocationOption.Clear) { Clear(); } diff --git a/Misaki.HighPerformance.Unsafe/Helpers/HashMapHelper.cs b/Misaki.HighPerformance.Unsafe/Helpers/HashMapHelper.cs index e65a400..c5e2b9e 100644 --- a/Misaki.HighPerformance.Unsafe/Helpers/HashMapHelper.cs +++ b/Misaki.HighPerformance.Unsafe/Helpers/HashMapHelper.cs @@ -1,5 +1,5 @@ -using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Unsafe.Collections; +using Misaki.HighPerformance.Unsafe.Services; using System.Numerics; using System.Runtime.CompilerServices; @@ -74,6 +74,7 @@ public unsafe struct HashMapHelper : IDisposable private readonly int _sizeOfTValue; private readonly int _log2MinGrowth; + private readonly Allocator _allocator; public const int MINIMAL_CAPACITY = 64; @@ -107,16 +108,6 @@ public unsafe struct HashMapHelper : IDisposable get => !IsCreated || _count == 0; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private readonly int CalcCapacityCeilPow2(int capacity) - { - capacity = Math.Max(Math.Max(1, _count), capacity); - var newCapacity = Math.Max(capacity, 1 << _log2MinGrowth); - var result = MathUtilities.CeilPow2(newCapacity); - - return result; - } - private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset) { var sizeOfTKey = sizeof(TKey); @@ -135,7 +126,7 @@ public unsafe struct HashMapHelper : IDisposable return totalSize; } - public HashMapHelper(int capacity, int sizeOfTValue, uint minGrowth) + public HashMapHelper(int capacity, int sizeOfTValue, uint minGrowth, Allocator allocator) { if (capacity <= 0) { @@ -150,18 +141,25 @@ public unsafe struct HashMapHelper : IDisposable _capacity = CalcCapacityCeilPow2(capacity); _bucketCapacity = _capacity * 2; + _sizeOfTValue = sizeOfTValue; + _log2MinGrowth = BitOperations.Log2(minGrowth); + _allocator = allocator; + var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue, out var keyOffset, out var nextOffset, out var bucketOffset); - _buffer = (byte*)Malloc((nuint)totalSize); - _keys = (TKey*)(_buffer + keyOffset); - _next = (int*)(_buffer + nextOffset); - _buckets = (int*)(_buffer + bucketOffset); - + AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset); Clear(); + } - _sizeOfTValue = sizeOfTValue; - _log2MinGrowth = BitOperations.Log2(minGrowth); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly int CalcCapacityCeilPow2(int capacity) + { + capacity = Math.Max(Math.Max(1, _count), capacity); + var newCapacity = Math.Max(capacity, 1 << _log2MinGrowth); + var result = MathUtilities.CeilPow2(newCapacity); + + return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -179,6 +177,17 @@ public unsafe struct HashMapHelper : IDisposable } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset) + { + var alignSize = sizeof(TKey) > sizeof(int) ? AlignOf() : AlignOf(); + + _buffer = AllocationManager.Allocate((uint)totalSize, (uint)alignSize, _allocator, AllocationOption.UnInitialized); + _keys = (TKey*)(_buffer + keyOffset); + _next = (int*)(_buffer + nextOffset); + _buckets = (int*)(_buffer + bucketOffset); + } + internal void ResizeExact(int newCapacity, int newBucketCapacity) { var totalSize = CalculateDataSize(newCapacity, newBucketCapacity, _sizeOfTValue, @@ -190,10 +199,7 @@ public unsafe struct HashMapHelper : IDisposable var oldBuckets = _buckets; var oldBucketCapacity = _bucketCapacity; - _buffer = (byte*)Malloc((nuint)totalSize); - _keys = (TKey*)(_buffer + keyOffset); - _next = (int*)(_buffer + nextOffset); - _buckets = (int*)(_buffer + bucketOffset); + AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset); _capacity = newCapacity; _bucketCapacity = newBucketCapacity; @@ -405,7 +411,7 @@ public unsafe struct HashMapHelper : IDisposable internal UnsafeArray GetKeyArray(Allocator allocator) { - var result = new UnsafeArray(_count, allocator, AllocationType.UnInitialized); + var result = new UnsafeArray(_count, allocator, AllocationOption.UnInitialized); for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; i++) { @@ -424,7 +430,7 @@ public unsafe struct HashMapHelper : IDisposable internal UnsafeArray GetValueArray(Allocator allocator) where TValue : unmanaged { - var result = new UnsafeArray(_count, allocator, AllocationType.UnInitialized); + var result = new UnsafeArray(_count, allocator, AllocationOption.UnInitialized); for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; ++i) { @@ -443,7 +449,7 @@ public unsafe struct HashMapHelper : IDisposable public UnsafeArray> GetKeyValueArrays(Allocator allocator) where TValue : unmanaged { - var result = new UnsafeArray>(_count, allocator, AllocationType.UnInitialized); + var result = new UnsafeArray>(_count, allocator, AllocationOption.UnInitialized); for (int i = 0, count = 0, max = result.Count, capacity = _bucketCapacity; i < capacity && count < max; i++) { @@ -476,7 +482,7 @@ public unsafe struct HashMapHelper : IDisposable { if (IsCreated) { - Free(_buffer); + AllocationManager.Free(_buffer, _allocator); _buffer = null; _keys = null; diff --git a/Misaki.HighPerformance.Unsafe/Misaki.HighPerformance.Unsafe.csproj b/Misaki.HighPerformance.Unsafe/Misaki.HighPerformance.Unsafe.csproj index 42b3bd0..8aeb221 100644 --- a/Misaki.HighPerformance.Unsafe/Misaki.HighPerformance.Unsafe.csproj +++ b/Misaki.HighPerformance.Unsafe/Misaki.HighPerformance.Unsafe.csproj @@ -16,7 +16,7 @@ - + diff --git a/Misaki.HighPerformance.Unsafe/Collections/Services/AllocationManager.cs b/Misaki.HighPerformance.Unsafe/Services/AllocationManager.cs similarity index 68% rename from Misaki.HighPerformance.Unsafe/Collections/Services/AllocationManager.cs rename to Misaki.HighPerformance.Unsafe/Services/AllocationManager.cs index e2e8339..da05851 100644 --- a/Misaki.HighPerformance.Unsafe/Collections/Services/AllocationManager.cs +++ b/Misaki.HighPerformance.Unsafe/Services/AllocationManager.cs @@ -1,6 +1,7 @@ using Misaki.HighPerformance.Unsafe.Buffer; +using Misaki.HighPerformance.Unsafe.Collections; -namespace Misaki.HighPerformance.Unsafe.Collections.Services; +namespace Misaki.HighPerformance.Unsafe.Services; public static unsafe class AllocationManager { @@ -15,7 +16,7 @@ public static unsafe class AllocationManager _initialized = true; } - public static T* Allocate(uint size, uint alignSize, Allocator allocator, AllocationType allocationType) + internal static T* Allocate(uint size, uint alignSize, Allocator allocator, AllocationOption allocationType) where T : unmanaged { if (!_initialized) @@ -34,6 +35,22 @@ public static unsafe class AllocationManager } } + internal static void Free(void* ptr, Allocator allocator) + { + if (!_initialized) + { + throw new InvalidOperationException("The AllocationManager has not been initialized."); + } + + lock (_lock) + { + if (allocator == Allocator.Persistent) + { + AlignedFree(ptr); + } + } + } + public static void Reset(bool clear = false) { if (!_initialized) diff --git a/Misaki.HighPerformance.sln b/Misaki.HighPerformance.sln index a1d7bbd..1ed6d0a 100644 --- a/Misaki.HighPerformance.sln +++ b/Misaki.HighPerformance.sln @@ -9,8 +9,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.Unsa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.Test", "Misaki.HighPerformance.Test\Misaki.HighPerformance.Test.csproj", "{90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.Mathematics", "Misaki.HighPerformance.Math\Misaki.HighPerformance.Mathematics.csproj", "{861F9574-2063-4B54-8D6A-AA32B26B6583}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -29,10 +27,6 @@ Global {90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}.Debug|Any CPU.Build.0 = Debug|Any CPU {90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}.Release|Any CPU.ActiveCfg = Release|Any CPU {90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}.Release|Any CPU.Build.0 = Release|Any CPU - {861F9574-2063-4B54-8D6A-AA32B26B6583}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {861F9574-2063-4B54-8D6A-AA32B26B6583}.Debug|Any CPU.Build.0 = Debug|Any CPU - {861F9574-2063-4B54-8D6A-AA32B26B6583}.Release|Any CPU.ActiveCfg = Release|Any CPU - {861F9574-2063-4B54-8D6A-AA32B26B6583}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Misaki.HighPerformance/MathUtilities.cs b/Misaki.HighPerformance/MathUtilities.cs new file mode 100644 index 0000000..183e8c9 --- /dev/null +++ b/Misaki.HighPerformance/MathUtilities.cs @@ -0,0 +1,19 @@ +using System.Runtime.CompilerServices; + +namespace Misaki.HighPerformance; + +public static class MathUtilities +{ + /// Returns the smallest power of two that is greater than or equal to the specified number. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CeilPow2(int x) + { + x -= 1; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; + } +} \ No newline at end of file