Upgrade to .NET 10 and refactor core components

Upgraded target framework to .NET 10 across all projects to leverage new features and improve performance.

Refactored `JobScheduler` to fix method naming inconsistencies and ensure proper resource disposal. Enhanced `AllocationManager` with safer memory operations and better performance handling. Simplified `ReadOnlyUnsafeCollection` enumerator logic for efficiency.

Overhauled `UnsafeBitSet` with new properties, improved bitwise operations, and optimized memory management. Updated `UnsafeSlotMap` and `ConcurrentSlotMap` for better validation and naming consistency.

Revised `MemoryLeakException` to use `ReadOnlySpan` for improved performance. Simplified `MathematicsBenchmark` logic and integrated `BenchmarkDotNet` for testing.

Added AOT compatibility settings for `Debug` and `Release` configurations. Introduced unit tests for `UnsafeBitSet` to validate functionality. Cleaned up unused code, improved readability, and ensured consistent naming conventions.

Updated project references and metadata for consistency. Enabled inline methods for `NET10_0_OR_GREATER` in `VectorGenerator`.
This commit is contained in:
2025-11-14 11:14:09 +09:00
parent bf4dd5670e
commit 24a7d49ae2
18 changed files with 223 additions and 260 deletions

View File

@@ -3,7 +3,7 @@
<TargetFrameworks></TargetFrameworks> <TargetFrameworks></TargetFrameworks>
<PackageLicenseUrl>Public Domain</PackageLicenseUrl> <PackageLicenseUrl>Public Domain</PackageLicenseUrl>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<AssemblyVersion>1.0.0</AssemblyVersion> <AssemblyVersion>1.0.0</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>

View File

@@ -30,10 +30,10 @@ public sealed unsafe class JobScheduler : IDisposable
private bool _disposed = false; private bool _disposed = false;
public int WorkerCount => _workerThreads.Length;
internal bool IsCancellationRequested => _cts.IsCancellationRequested; internal bool IsCancellationRequested => _cts.IsCancellationRequested;
public int WorkerCount => _workerThreads.Length;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JobScheduler"/> class with the specified number of worker threads. /// Initializes a new instance of the <see cref="JobScheduler"/> class with the specified number of worker threads.
/// </summary> /// </summary>
@@ -481,7 +481,7 @@ public sealed unsafe class JobScheduler : IDisposable
var completedCount = 0; var completedCount = 0;
foreach (var handle in handles) foreach (var handle in handles)
{ {
if (!_jobInfoPool.Contain(handle._id, handle._generation)) if (!_jobInfoPool.Contains(handle._id, handle._generation))
{ {
completedCount++; completedCount++;
} }
@@ -514,7 +514,7 @@ public sealed unsafe class JobScheduler : IDisposable
{ {
foreach (var handle in handles) foreach (var handle in handles)
{ {
if (!_jobInfoPool.Contain(handle._id, handle._generation)) if (!_jobInfoPool.Contains(handle._id, handle._generation))
{ {
return handle; return handle;
} }
@@ -542,6 +542,7 @@ public sealed unsafe class JobScheduler : IDisposable
_jobQueue.Clear(); _jobQueue.Clear();
_jobDataAllocator.Dispose(); _jobDataAllocator.Dispose();
_workSignal.Dispose();
_cts.Dispose(); _cts.Dispose();
_disposed = true; _disposed = true;

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
@@ -14,8 +14,17 @@
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl> <RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Misaki.HighPerformance.LowLevel\Misaki.HighPerformance.LowLevel.csproj" /> <ProjectReference Include="..\Misaki.HighPerformance.LowLevel\Misaki.HighPerformance.LowLevel.csproj" />
<ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -8,7 +8,7 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary> /// <summary>
/// Holds information about a memory allocation. /// Holds information about a memory allocation.
/// </summary> /// </summary>
public readonly unsafe struct AllocationInfo public readonly struct AllocationInfo
{ {
/// <summary> /// <summary>
/// Get the size of the allocation in bytes. /// Get the size of the allocation in bytes.
@@ -69,7 +69,7 @@ public static unsafe class AllocationManager
{ {
var selfPtr = (ArenaAllocator*)instance; var selfPtr = (ArenaAllocator*)instance;
var newPtr = selfPtr->_arena.Allocate(newSize, alignment, allocationOption); var newPtr = selfPtr->_arena.Allocate(newSize, alignment, allocationOption);
MemCpy(newPtr, ptr, newSize); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
if (allocationOption.HasFlag(AllocationOption.Clear)) if (allocationOption.HasFlag(AllocationOption.Clear))
{ {
@@ -149,7 +149,7 @@ public static unsafe class AllocationManager
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption) private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
{ {
var newPtr = s_stack.Allocate(newSize, alignment, AllocationOption.None); var newPtr = s_stack.Allocate(newSize, alignment, AllocationOption.None);
MemCpy(newPtr, ptr, newSize); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
if (allocationOption.HasFlag(AllocationOption.Clear)) if (allocationOption.HasFlag(AllocationOption.Clear))
{ {
@@ -174,7 +174,7 @@ public static unsafe class AllocationManager
} }
} }
private const uint _DEFAULT_MEMORY_POOL_SIZE = 512 * 1024; private const uint _DEFAULT_MEMORY_POOL_SIZE = 512 * 1024; // 512 KB
private static readonly ArenaAllocator* s_pArenaAllocator; private static readonly ArenaAllocator* s_pArenaAllocator;
private static readonly HeapAllocator* s_pHeapAllocator; private static readonly HeapAllocator* s_pHeapAllocator;
@@ -438,12 +438,10 @@ public static unsafe class AllocationManager
} }
var newPtr = AlignedRealloc(ptr, newSize, alignment); var newPtr = AlignedRealloc(ptr, newSize, alignment);
if (allocationOption.HasFlag(AllocationOption.Clear)) if (allocationOption.HasFlag(AllocationOption.Clear)
&& newSize > oldSize)
{ {
if (newSize > oldSize) MemClear((byte*)newPtr + oldSize, newSize - oldSize);
{
MemClear((byte*)newPtr + oldSize, newSize - oldSize);
}
} }
return newPtr; return newPtr;
@@ -535,7 +533,7 @@ public static unsafe class AllocationManager
if (unfreeBytes > 0u) if (unfreeBytes > 0u)
{ {
throw new MemoryLeakException(snapshot); throw new MemoryLeakException(CollectionsMarshal.AsSpan(snapshot));
} }
} }
else if (s_activeHeapAllocations != 0) else if (s_activeHeapAllocations != 0)

View File

@@ -1,4 +1,4 @@
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections; using System.Collections;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -20,36 +20,21 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
{ {
private readonly ReadOnlyUnsafeCollection<T> _collection; private readonly ReadOnlyUnsafeCollection<T> _collection;
private int _index; private int _index;
private T _value;
public readonly T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _value;
}
public readonly T Current => _collection[_index];
readonly object IEnumerator.Current => Current; readonly object IEnumerator.Current => Current;
public Enumerator(ref readonly ReadOnlyUnsafeCollection<T> array) public Enumerator(ref readonly ReadOnlyUnsafeCollection<T> array)
{ {
_collection = array; _collection = array;
_index = -1; _index = -1;
_value = default;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() public bool MoveNext()
{ {
_index++; _index++;
return _index < _collection.Count;
if (_index < _collection.Count)
{
_value = UnsafeUtility.ReadArrayElement<T>(_collection._buffer, _index);
return true;
}
_value = default;
return false;
} }
public void Reset() public void Reset()
@@ -79,7 +64,8 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
get => UnsafeUtility.ReadArrayElement<T>(_buffer, index); get => UnsafeUtility.ReadArrayElement<T>(_buffer, index);
} }
public IEnumerator<T> GetEnumerator() => new Enumerator(in this); public Enumerator GetEnumerator() => new Enumerator(in this);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public ReadOnlyUnsafeCollection(T* buffer, int count) public ReadOnlyUnsafeCollection(T* buffer, int count)

View File

@@ -14,47 +14,38 @@ public unsafe struct UnsafeBitSet : IDisposable
private const int _MASK = (1 << 5) - 1; // 0x1F, the mask to get the bit index inside a uint private const int _MASK = (1 << 5) - 1; // 0x1F, the mask to get the bit index inside a uint
private static readonly int s_padding = Vector<uint>.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically private static readonly int s_padding = Vector<uint>.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically
/// <summary>
/// Determines the required length of an <see cref="UnsafeBitSet"/> to hold the passed id or bit.
/// </summary>
/// <param name="id">The id or bit.</param>
/// <returns>A size of required <see cref="uint"/>s for the bitset.</returns>
public static int RequiredLength(int id)
{
return (id >> 5) + int.Sign(id & _BIT_SIZE);
}
/// <summary>
/// Rounds the given length to the next padding size.
/// </summary>
/// <param name="length">The length to round.</param>
/// <returns>The rounded length.</returns>
public static int RoundToPadding(int length)
{
return (length + s_padding - 1) / s_padding * s_padding;
}
/// <summary>
/// The bits from the bitset.
/// </summary>
private UnsafeArray<uint> _bits; private UnsafeArray<uint> _bits;
private int _highestBit;
private int _max;
/// <summary>
/// The highest uint index in use inside the <see cref="_bits"/>-array.
/// </summary>
public readonly int HighestIndex => _max;
/// <summary> /// <summary>
/// The highest bit set. /// The highest bit set.
/// </summary> /// </summary>
private int _highestBit; public readonly int HighestBit => _highestBit;
/// <summary> /// <summary>
/// The maximum <see cref="_bits"/>-index current in use. /// Returns the count of the bitset, how many uints it consists of.
/// </summary> /// </summary>
private int _max; public readonly int Count => _bits.Count;
/// <summary>
/// Gets the total number of bits represented by the current instance.
/// </summary>
public readonly int BitCount => _bits.Count << _INDEX_SIZE;
public readonly bool IsCreated => _bits.IsCreated;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UnsafeBitSet" /> class. /// Initializes a new instance of the <see cref="UnsafeBitSet" /> class.
/// </summary> /// </summary>
public UnsafeBitSet() public UnsafeBitSet()
{ {
_bits = new UnsafeArray<uint>(s_padding, Allocator.Persistent, AllocationOption.None); _bits = new UnsafeArray<uint>(0, Allocator.Invalid, AllocationOption.None);
} }
/// <summary> /// <summary>
@@ -94,33 +85,19 @@ public unsafe struct UnsafeBitSet : IDisposable
} }
} }
/// <summary> private static int RoundToPadding(int length)
/// The highest uint index in use inside the <see cref="_bits"/>-array.
/// </summary>
public int HighestIndex
{ {
get => _max; return (length + s_padding - 1) / s_padding * s_padding;
} }
/// <summary> /// <summary>
/// The highest bit set. /// Determines the required length of an <see cref="UnsafeBitSet"/> to hold the passed id or bit.
/// </summary> /// </summary>
public int HighestBit /// <param name="id">The id or bit.</param>
/// <returns>A size of required <see cref="uint"/>s for the bitset.</returns>
public static int RequiredLength(int id)
{ {
get => _highestBit; return (id >> _INDEX_SIZE) + int.Sign(id & _BIT_SIZE);
}
/// <summary>
/// Returns the length of the bitset, how many ints it consists of.
/// </summary>
public int Length
{
get => _bits.Count;
}
public bool IsCreated
{
get => _bits.IsCreated;
} }
/// <summary> /// <summary>
@@ -128,7 +105,7 @@ public unsafe struct UnsafeBitSet : IDisposable
/// </summary> /// </summary>
/// <param name="index">The index.</param> /// <param name="index">The index.</param>
/// <returns>True if it is, otherwise false</returns> /// <returns>True if it is, otherwise false</returns>
public bool IsSet(int index) public readonly bool IsSet(int index)
{ {
var b = index >> _INDEX_SIZE; var b = index >> _INDEX_SIZE;
if (b >= _bits.Count) if (b >= _bits.Count)
@@ -149,9 +126,10 @@ public unsafe struct UnsafeBitSet : IDisposable
var b = index >> _INDEX_SIZE; var b = index >> _INDEX_SIZE;
if (b >= _bits.Count) if (b >= _bits.Count)
{ {
_bits.Resize(RoundToPadding(b)); _bits.Resize(index);
} }
// Track highest set bit // Track highest set bit
_highestBit = Math.Max(_highestBit, index); _highestBit = Math.Max(_highestBit, index);
_max = _highestBit / (_BIT_SIZE + 1) + 1; _max = _highestBit / (_BIT_SIZE + 1) + 1;
@@ -228,9 +206,12 @@ public unsafe struct UnsafeBitSet : IDisposable
public void Resize(int minimalLength, AllocationOption option = AllocationOption.None) public void Resize(int minimalLength, AllocationOption option = AllocationOption.None)
{ {
var oldSize = _bits.Count;
var uints = (minimalLength >> _INDEX_SIZE) + int.Sign(minimalLength & _BIT_SIZE); var uints = (minimalLength >> _INDEX_SIZE) + int.Sign(minimalLength & _BIT_SIZE);
var length = RoundToPadding(uints); var length = RoundToPadding(uints);
_bits.Resize(length, option); _bits.Resize(length, option);
_bits.AsSpan()[oldSize..].Clear();
} }
/// <summary> /// <summary>
@@ -238,10 +219,9 @@ public unsafe struct UnsafeBitSet : IDisposable
/// </summary> /// </summary>
/// <param name="other">The other <see cref="UnsafeBitSet"/>.</param> /// <param name="other">The other <see cref="UnsafeBitSet"/>.</param>
/// <returns>True if they match, false if not.</returns> /// <returns>True if they match, false if not.</returns>
[SkipLocalsInit] public readonly bool All(UnsafeBitSet other)
public bool All(UnsafeBitSet other)
{ {
var min = Math.Min(Math.Min(Length, other.Length), _max); var min = Math.Min(Math.Min(Count, other.Count), _max);
if (!Vector.IsHardwareAccelerated || min < s_padding) if (!Vector.IsHardwareAccelerated || min < s_padding)
{ {
var bits = _bits.AsSpan(); var bits = _bits.AsSpan();
@@ -300,9 +280,9 @@ public unsafe struct UnsafeBitSet : IDisposable
/// </summary> /// </summary>
/// <param name="other">The other <see cref="UnsafeBitSet"/>.</param> /// <param name="other">The other <see cref="UnsafeBitSet"/>.</param>
/// <returns>True if they match, false if not.</returns> /// <returns>True if they match, false if not.</returns>
public bool Any(UnsafeBitSet other) public readonly bool Any(UnsafeBitSet other)
{ {
var min = Math.Min(Math.Min(Length, other.Length), _max); var min = Math.Min(Math.Min(Count, other.Count), _max);
if (!Vector.IsHardwareAccelerated || min < s_padding) if (!Vector.IsHardwareAccelerated || min < s_padding)
{ {
var bits = _bits.AsSpan(); var bits = _bits.AsSpan();
@@ -361,9 +341,9 @@ public unsafe struct UnsafeBitSet : IDisposable
/// </summary> /// </summary>
/// <param name="other">The other <see cref="UnsafeBitSet"/>.</param> /// <param name="other">The other <see cref="UnsafeBitSet"/>.</param>
/// <returns>True if none match, false if not.</returns> /// <returns>True if none match, false if not.</returns>
public bool None(UnsafeBitSet other) public readonly bool None(UnsafeBitSet other)
{ {
var min = Math.Min(Math.Min(Length, other.Length), _max); var min = Math.Min(Math.Min(Count, other.Count), _max);
if (!Vector.IsHardwareAccelerated || min < s_padding) if (!Vector.IsHardwareAccelerated || min < s_padding)
{ {
var bits = _bits.AsSpan(); var bits = _bits.AsSpan();
@@ -403,9 +383,9 @@ public unsafe struct UnsafeBitSet : IDisposable
/// </summary> /// </summary>
/// <param name="other">The other <see cref="UnsafeBitSet"/>.</param> /// <param name="other">The other <see cref="UnsafeBitSet"/>.</param>
/// <returns>True if they match, false if not.</returns> /// <returns>True if they match, false if not.</returns>
public bool Exclusive(UnsafeBitSet other) public readonly bool Exclusive(UnsafeBitSet other)
{ {
var min = Math.Min(Math.Min(Length, other.Length), _max); var min = Math.Min(Math.Min(Count, other.Count), _max);
if (!Vector.IsHardwareAccelerated || min < s_padding) if (!Vector.IsHardwareAccelerated || min < s_padding)
{ {
@@ -460,152 +440,85 @@ public unsafe struct UnsafeBitSet : IDisposable
return true; return true;
} }
public unsafe void AndOperation(UnsafeBitSet other) public void And(UnsafeBitSet other)
{ {
var min = Math.Min(Length, other.Length); if (Count != other.Count)
var temp = stackalloc uint[min];
if (!Vector.IsHardwareAccelerated || min < s_padding)
{ {
for (var i = 0; i < min; i++) throw new ArgumentException("Bitsets must be of the same length for AND operation.");
}
if (!Vector.IsHardwareAccelerated || Count < s_padding)
{
for (var i = 0; i < Count; i++)
{ {
temp[i] = _bits[i] & other._bits[i]; _bits[i] &= other._bits[i];
} }
} }
else else
{ {
for (var i = 0; i < min; i += s_padding) for (var i = 0; i < Count; i += s_padding)
{ {
var vectorLeft = new Vector<uint>(_bits.AsSpan()[i..]); var vectorLeft = new Vector<uint>(_bits.AsSpan()[i..]);
var vectorRight = new Vector<uint>(other._bits.AsSpan()[i..]); var vectorRight = new Vector<uint>(other._bits.AsSpan()[i..]);
var resultVector = Vector.BitwiseAnd(vectorLeft, vectorRight); var resultVector = Vector.BitwiseAnd(vectorLeft, vectorRight);
resultVector.CopyTo(new Span<uint>(temp + i, s_padding));
resultVector.CopyTo(_bits.AsSpan(i, s_padding));
} }
} }
_bits.CopyFrom(new Span<uint>(temp, min));
} }
public unsafe void OrOperation(UnsafeBitSet other) public void Or(UnsafeBitSet other)
{ {
var min = Math.Min(Length, other.Length); if (Count != other.Count)
var temp = stackalloc uint[min];
if (!Vector.IsHardwareAccelerated || min < s_padding)
{ {
for (var i = 0; i < min; i++) throw new ArgumentException("Bitsets must be of the same length for AND operation.");
}
if (!Vector.IsHardwareAccelerated || Count < s_padding)
{
for (var i = 0; i < Count; i++)
{ {
temp[i] = _bits[i] | other._bits[i]; _bits[i] |= other._bits[i];
} }
} }
else else
{ {
for (var i = 0; i < min; i += s_padding) for (var i = 0; i < Count; i += s_padding)
{ {
var vectorLeft = new Vector<uint>(_bits.AsSpan()[i..]); var vectorLeft = new Vector<uint>(_bits.AsSpan()[i..]);
var vectorRight = new Vector<uint>(other._bits.AsSpan()[i..]); var vectorRight = new Vector<uint>(other._bits.AsSpan()[i..]);
var resultVector = Vector.BitwiseOr(vectorLeft, vectorRight); var resultVector = Vector.BitwiseOr(vectorLeft, vectorRight);
resultVector.CopyTo(new Span<uint>(temp + i, s_padding));
resultVector.CopyTo(_bits.AsSpan(i, s_padding));
} }
} }
_bits.CopyFrom(new Span<uint>(temp, min));
} }
public unsafe void XorOperation(UnsafeBitSet other) public void Xor(UnsafeBitSet other)
{ {
var min = Math.Min(Length, other.Length); if (Count != other.Count)
var temp = stackalloc uint[min];
if (!Vector.IsHardwareAccelerated || min < s_padding)
{ {
for (var i = 0; i < min; i++) throw new ArgumentException("Bitsets must be of the same length for AND operation.");
}
if (!Vector.IsHardwareAccelerated || Count < s_padding)
{
for (var i = 0; i < Count; i++)
{ {
temp[i] = _bits[i] ^ other._bits[i]; _bits[i] ^= other._bits[i];
} }
} }
else else
{ {
for (var i = 0; i < min; i += s_padding) for (var i = 0; i < Count; i += s_padding)
{ {
var vectorLeft = new Vector<uint>(_bits.AsSpan()[i..]); var vectorLeft = new Vector<uint>(_bits.AsSpan()[i..]);
var vectorRight = new Vector<uint>(other._bits.AsSpan()[i..]); var vectorRight = new Vector<uint>(other._bits.AsSpan()[i..]);
var resultVector = Vector.Xor(vectorLeft, vectorRight); var resultVector = Vector.Xor(vectorLeft, vectorRight);
resultVector.CopyTo(new Span<uint>(temp + i, s_padding));
}
}
_bits.CopyFrom(new Span<uint>(temp, min)); resultVector.CopyTo(_bits.AsSpan(i, s_padding));
}
public static UnsafeBitSet operator &(UnsafeBitSet left, UnsafeBitSet right)
{
var min = Math.Min(left.Length, right.Length);
var result = new UnsafeBitSet(min, Allocator.Persistent);
if (!Vector.IsHardwareAccelerated || min < s_padding)
{
for (var i = 0; i < min; i++)
{
result._bits[i] = left._bits[i] & right._bits[i];
} }
} }
else
{
for (var i = 0; i < min; i += s_padding)
{
var vectorLeft = new Vector<uint>(left._bits.AsSpan()[i..]);
var vectorRight = new Vector<uint>(right._bits.AsSpan()[i..]);
var resultVector = Vector.BitwiseAnd(vectorLeft, vectorRight);
resultVector.CopyTo(result._bits.AsSpan(i, s_padding));
}
}
return result;
}
public static UnsafeBitSet operator |(UnsafeBitSet left, UnsafeBitSet right)
{
var min = Math.Min(left.Length, right.Length);
var result = new UnsafeBitSet(min, Allocator.Persistent);
if (!Vector.IsHardwareAccelerated || min < s_padding)
{
for (var i = 0; i < min; i++)
{
result._bits[i] = left._bits[i] | right._bits[i];
}
}
else
{
for (var i = 0; i < min; i += s_padding)
{
var vectorLeft = new Vector<uint>(left._bits.AsSpan()[i..]);
var vectorRight = new Vector<uint>(right._bits.AsSpan()[i..]);
var resultVector = Vector.BitwiseOr(vectorLeft, vectorRight);
resultVector.CopyTo(result._bits.AsSpan(i, s_padding));
}
}
return result;
}
public static UnsafeBitSet operator ~(UnsafeBitSet bitSet)
{
if (!Vector.IsHardwareAccelerated || bitSet.Length < s_padding)
{
for (var i = 0; i < bitSet.Length; i++)
{
bitSet._bits[i] = ~bitSet._bits[i];
}
}
else
{
for (var i = 0; i < bitSet.Length; i += s_padding)
{
var vector = new Vector<uint>(bitSet._bits.AsSpan()[i..]);
var resultVector = ~vector;
resultVector.CopyTo(bitSet._bits.AsSpan(i, s_padding));
}
}
return bitSet;
} }
/// <summary> /// <summary>
@@ -624,10 +537,10 @@ public unsafe struct UnsafeBitSet : IDisposable
/// <param name="span">The <see cref="Span{T}"/> to copy into.</param> /// <param name="span">The <see cref="Span{T}"/> to copy into.</param>
/// <param name="zero">If true, it will zero the unused space from the <see cref="span"/>.</param> /// <param name="zero">If true, it will zero the unused space from the <see cref="span"/>.</param>
/// <returns>The <see cref="Span{T}"/>.</returns> /// <returns>The <see cref="Span{T}"/>.</returns>
public Span<uint> AsSpan(Span<uint> span, bool zero = true) public readonly Span<uint> AsSpan(Span<uint> span, bool zero = true)
{ {
// Copy everything thats possible from one to another // Copy everything thats possible from one to another
var length = Math.Min(Length, span.Length); var length = Math.Min(Count, span.Length);
for (var index = 0; index < length; index++) for (var index = 0; index < length; index++)
{ {
span[index] = _bits[index]; span[index] = _bits[index];
@@ -639,10 +552,10 @@ public unsafe struct UnsafeBitSet : IDisposable
span[index] = 0; span[index] = 0;
} }
return span[..Length]; return span[..Count];
} }
public override string ToString() public readonly override string ToString()
{ {
// Convert uint to binary form for pretty printing // Convert uint to binary form for pretty printing
var binaryBuilder = new StringBuilder(); var binaryBuilder = new StringBuilder();
@@ -652,7 +565,7 @@ public unsafe struct UnsafeBitSet : IDisposable
} }
binaryBuilder.Length--; binaryBuilder.Length--;
return $"{nameof(_bits)}: {binaryBuilder}, {nameof(Length)}: {Length}"; return $"{nameof(_bits)}: {binaryBuilder}, {nameof(Count)}: {Count}";
} }
public void Dispose() public void Dispose()

View File

@@ -57,7 +57,7 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
public readonly int Count => _count; public readonly int Count => _count;
public readonly int Capacity => _capacity; public readonly int Capacity => _capacity;
public readonly bool IsCreated => _data.IsCreated && _freeSlots.IsCreated; public readonly bool IsCreated => _data.IsCreated && _generations.IsCreated && _freeSlots.IsCreated && _validBits.IsCreated;
public Enumerator GetEnumerator() => new((UnsafeSlotMap<T>*)UnsafeUtility.AddressOf(ref this)); public Enumerator GetEnumerator() => new((UnsafeSlotMap<T>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
@@ -239,7 +239,7 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
throw new ArgumentOutOfRangeException(nameof(slotIndex), "Slot index is out of range."); throw new ArgumentOutOfRangeException(nameof(slotIndex), "Slot index is out of range.");
} }
if (!_validBits.IsSet(slotIndex)|| _generations[slotIndex] != generation) if (!_validBits.IsSet(slotIndex) || _generations[slotIndex] != generation)
{ {
throw new InvalidOperationException($"Slot {slotIndex} is not occupied."); throw new InvalidOperationException($"Slot {slotIndex} is not occupied.");
} }
@@ -294,7 +294,7 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
_count = 0; _count = 0;
} }
public readonly unsafe void* GetUnsafePtr() public readonly void* GetUnsafePtr()
{ {
return _data.GetUnsafePtr(); return _data.GetUnsafePtr();
} }
@@ -303,6 +303,7 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
{ {
_data.Dispose(); _data.Dispose();
_freeSlots.Dispose(); _freeSlots.Dispose();
_validBits.Dispose();
_count = 0; _count = 0;
_capacity = 0; _capacity = 0;

View File

@@ -10,12 +10,21 @@ namespace Misaki.HighPerformance.LowLevel;
/// <param name="Infos">An array of AllocationInfo containing details about the memory leaks.</param> /// <param name="Infos">An array of AllocationInfo containing details about the memory leaks.</param>
public class MemoryLeakException : Exception public class MemoryLeakException : Exception
{ {
private readonly IEnumerable<AllocationInfo>? _infos; private readonly string _message;
private readonly string _message = string.Empty;
public MemoryLeakException(IEnumerable<AllocationInfo> infos) public override string Message => _message;
public MemoryLeakException(ReadOnlySpan<AllocationInfo> infos)
{ {
_infos = infos; var stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"Found {infos.Length} memory lakes!");
foreach (var info in infos)
{
stringBuilder.AppendLine(GetMessage(info.StackTrace));
}
_message = stringBuilder.ToString();
} }
public MemoryLeakException(string message) public MemoryLeakException(string message)
@@ -44,25 +53,4 @@ public class MemoryLeakException : Exception
return stringBuilder.ToString(); return stringBuilder.ToString();
} }
public override string Message
{
get
{
if (_infos == null)
{
return _message;
}
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"Found {_infos.Count()} memory lakes!");
foreach (var info in _infos)
{
stringBuilder.AppendLine(GetMessage(info.StackTrace));
}
return stringBuilder.ToString();
}
}
} }

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
@@ -21,10 +21,6 @@
<IsAotCompatible>True</IsAotCompatible> <IsAotCompatible>True</IsAotCompatible>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="Collections\FixedText.tt"> <None Update="Collections\FixedText.tt">
<Generator>TextTemplatingFileGenerator</Generator> <Generator>TextTemplatingFileGenerator</Generator>

View File

@@ -422,7 +422,7 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs + rhs.{c}"))}); return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs + rhs.{c}"))});
}} }}
#if false //NET10_0_OR_GREATER #if NET10_0_OR_GREATER
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
public void operator +=({typeName} other) public void operator +=({typeName} other)
{{"); {{");

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
@@ -13,6 +13,14 @@
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl> <RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Misaki.HighPerformance.Mathematics.CodeGen\Misaki.HighPerformance.Mathematics.CodeGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> <ProjectReference Include="..\Misaki.HighPerformance.Mathematics.CodeGen\Misaki.HighPerformance.Mathematics.CodeGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup> </ItemGroup>

View File

@@ -1,7 +1,6 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
namespace Misaki.HighPerformance.Test.Benchmark; namespace Misaki.HighPerformance.Test.Benchmark;
@@ -25,7 +24,7 @@ public unsafe class MathematicsBenchmark
public static f4 operator +(f4 a, f4 b) public static f4 operator +(f4 a, f4 b)
{ {
var result = a._vec + b._vec; var result = a._vec + b._vec;
return Unsafe.As<Vector128<float>, f4>(ref result); return new f4(result);
} }
} }
@@ -108,7 +107,7 @@ public unsafe class MathematicsBenchmark
} }
[Benchmark] [Benchmark]
public unsafe Vector128<float> v128Add() public Vector128<float> v128Add()
{ {
var a = Vector128.Create(1f, 2f, 3f, 4f); var a = Vector128.Create(1f, 2f, 3f, 4f);
var b = Vector128.Create(5f, 6f, 7f, 8f); var b = Vector128.Create(5f, 6f, 7f, 8f);

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PublishAot>True</PublishAot> <PublishAot>True</PublishAot>

View File

@@ -20,7 +20,7 @@
//using Misaki.HighPerformance.Test.Benchmark; //using Misaki.HighPerformance.Test.Benchmark;
//BenchmarkDotNet.Running.BenchmarkRunner.Run<MathematicsBenchmark>(); BenchmarkDotNet.Running.BenchmarkRunner.Run<Misaki.HighPerformance.Test.Benchmark.MathematicsBenchmark>();
//using Misaki.HighPerformance.LowLevel.Buffer; //using Misaki.HighPerformance.LowLevel.Buffer;
//using Misaki.HighPerformance.LowLevel.Collections; //using Misaki.HighPerformance.LowLevel.Collections;
@@ -40,17 +40,3 @@
// } // }
//} //}
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
//AllocationManager.EnableDebugLayer();
//var array = new UnsafeArray<int>(10, Allocator.Persistent);
//var array2 = new UnsafeArray<int>(10, Allocator.Persistent);
//array.Dispose();
//array2.Dispose();
//AllocationManager.Dispose();
using (AllocationManager.CreateStackScope())
{
var arr = new UnsafeArray<int>(10, Allocator.Stack);
}

View File

@@ -0,0 +1,78 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
[TestClass]
public class TestUnsafeBitSet
{
private UnsafeBitSet _set1;
private UnsafeBitSet _set2;
[TestInitialize]
public void Initialize()
{
_set1 = new UnsafeBitSet(16, Allocator.Persistent, AllocationOption.Clear);
_set2 = new UnsafeBitSet(16, Allocator.Persistent, AllocationOption.Clear);
}
[TestCleanup]
public void Cleanup()
{
_set1.Dispose();
_set2.Dispose();
}
[TestMethod]
public void TestBitCount()
{
Assert.AreEqual(256, _set1.BitCount);
}
[TestMethod]
public void TestSetAndGet()
{
Assert.IsFalse(_set1.IsSet(0));
_set1.SetBit(0);
Assert.IsTrue(_set1.IsSet(0));
_set1.ClearBit(0);
Assert.IsFalse(_set1.IsSet(0));
}
[TestMethod]
public void TestClearAll()
{
for (int i = 0; i < _set1.BitCount; i++)
{
_set1.SetBit(i);
}
_set1.ClearAll();
for (int i = 0; i < _set1.BitCount; i++)
{
Assert.IsFalse(_set1.IsSet(i));
}
}
[TestMethod]
public void TestAndOperation()
{
_set1.SetBit(0);
_set1.SetBit(1);
_set2.SetBit(1);
_set2.SetBit(2);
_set1.And(_set2);
Assert.IsFalse(_set1.IsSet(0));
Assert.IsTrue(_set1.IsSet(1));
Assert.IsFalse(_set1.IsSet(2));
}
}

View File

@@ -11,7 +11,7 @@ public class TestUnsafeSlotMap
[TestInitialize] [TestInitialize]
public void Initialize() public void Initialize()
{ {
_slotMap = new UnsafeSlotMap<int>(16, Allocator.Persistent, AllocationOption.Clear); _slotMap = new UnsafeSlotMap<int>(16, Allocator.Persistent);
} }
[TestCleanup] [TestCleanup]

View File

@@ -1,4 +1,4 @@
using System.Collections; using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -207,7 +207,7 @@ public class ConcurrentSlotMap<T> : IEnumerable<T>
return false; // Another thread already removed it return false; // Another thread already removed it
} }
public bool Contain(int slotIndex, int generation) public bool Contains(int slotIndex, int generation)
{ {
if (slotIndex < 0 || slotIndex >= Volatile.Read(ref _capacity)) if (slotIndex < 0 || slotIndex >= Volatile.Read(ref _capacity))
{ {

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>