Compare commits

4 Commits

Author SHA1 Message Date
53bba54d43 fixed generic types cannot have explicit layout issue 2026-05-29 08:43:33 +09:00
7c9612ceb0 Improve spin-wait, SPMCQueue, and add UnsafeString/Text
- JobScheduler: dynamic spin-wait thresholds for lower latency; removed SLEEP_THRESHOLD constant.
- SPMCQueue: switched to explicit struct layout, removed manual padding, fixed power-of-two capacity bug.
- WorkerThread: enhanced work-stealing with additional cascade loop.
- AllocationHandle: added IsValid property.
- VirtualMemoryBlock: fixed Dispose to pass correct size to Munmap.
- Added UnsafeString and UnsafeText for zero-allocation string/text handling.
- Updated project metadata, versions, and repository URLs.
- Minor inlining/optimization tweaks in GGXMipGenerationBenchmark.
2026-05-29 00:48:36 +09:00
fef20f05b7 updated GGXMipGenerationJobSPMD to use IJobParallel 2026-05-20 19:19:45 +09:00
27569e9eb5 added IUnsafeBitSet 2026-05-19 11:35:32 +09:00
11 changed files with 230 additions and 67 deletions

View File

@@ -121,9 +121,6 @@ internal sealed class WaitAnyItem : IThreadPoolWorkItem
/// </summary> /// </summary>
public sealed unsafe partial class JobScheduler : IDisposable public sealed unsafe partial class JobScheduler : IDisposable
{ {
// Don't sleep indefinitely because that causes our 1ms job to become 15ms.
private const int SLEEP_THRESHOLD = -1;
private readonly ConcurrentSlotMap<JobInfo> _jobInfoPool; private readonly ConcurrentSlotMap<JobInfo> _jobInfoPool;
private readonly ConcurrentQueue<JobHandle>[] _jobQueues; private readonly ConcurrentQueue<JobHandle>[] _jobQueues;
private readonly WorkerThread[] _workerThreads; private readonly WorkerThread[] _workerThreads;
@@ -834,7 +831,7 @@ public sealed unsafe partial class JobScheduler : IDisposable
if (!madeProgress) if (!madeProgress)
{ {
spin.SpinOnce(SLEEP_THRESHOLD); spin.SpinOnce(-1); // Never sleep and yield to achieve lowest latency for single job completion.
} }
} }
} }
@@ -854,6 +851,7 @@ public sealed unsafe partial class JobScheduler : IDisposable
} }
var spin = new SpinWait(); var spin = new SpinWait();
var sleepThreshold = handles.Length * 20;
var completedCount = 0; var completedCount = 0;
while (true) while (true)
@@ -877,7 +875,7 @@ public sealed unsafe partial class JobScheduler : IDisposable
return; return;
} }
spin.SpinOnce(SLEEP_THRESHOLD); spin.SpinOnce(sleepThreshold);
} }
} }
@@ -889,6 +887,7 @@ public sealed unsafe partial class JobScheduler : IDisposable
public JobHandle WaitAny(params ReadOnlySpan<JobHandle> handles) public JobHandle WaitAny(params ReadOnlySpan<JobHandle> handles)
{ {
var spin = new SpinWait(); var spin = new SpinWait();
var sleepThreshold = handles.Length * 10;
while (true) while (true)
{ {
@@ -900,7 +899,7 @@ public sealed unsafe partial class JobScheduler : IDisposable
} }
} }
spin.SpinOnce(SLEEP_THRESHOLD); spin.SpinOnce(sleepThreshold);
} }
} }

View File

@@ -6,13 +6,20 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<AssemblyVersion>3.1.7</AssemblyVersion> <AssemblyVersion>3.1.8</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl> <PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl> <RepositoryUrl>https://github.com/misakieku/Misaki.HighPerformance.git</RepositoryUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>job-system;multithreading;concurrency;task-scheduler;work-stealing;zero-allocation;0gc;dag;dependency-graph;game-engine;ecs;high-performance;spmc</PackageTags>
<Description>
A high-performance, zero-allocation (0 GC), and zero-closure job system designed for custom game engines and data-oriented design (DOD).
Features a lock-free Work-Stealing scheduler (SPMC) with DAG-based multi-dependency resolution.
Includes a blazingly fast O(1) branchless priority queue (High/Normal/Low) using Cascade LUTs.
Uniquely supports both unmanaged and managed jobs seamlessly via internal pooling, offering maximum flexibility without compromising C# GC performance.
</Description>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -7,16 +7,16 @@ namespace Misaki.HighPerformance.Jobs;
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public class SPMCQueue<T> public class SPMCQueue<T>
{ {
private unsafe struct padding private struct __padding
{ {
private fixed byte _padding[64]; private unsafe fixed byte _padding[64];
} }
private readonly T[] _queue; private readonly T[] _queue;
private readonly int _mask; private readonly int _mask;
private int _head; private int _head;
private padding _padding; // Prevent false sharing between head and tail private __padding _padding;
private int _tail; private int _tail;
public bool IsEmpty => Volatile.Read(ref _tail) - Volatile.Read(ref _head) <= 0; public bool IsEmpty => Volatile.Read(ref _tail) - Volatile.Read(ref _head) <= 0;
@@ -30,8 +30,9 @@ public class SPMCQueue<T>
/// <param name="capacity">The capacity of the queue.</param> /// <param name="capacity">The capacity of the queue.</param>
public SPMCQueue(int capacity) public SPMCQueue(int capacity)
{ {
_queue = new T[(int)BitOperations.RoundUpToPowerOf2((uint)capacity)]; var powerOfTwoCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)capacity);
_mask = capacity - 1; _queue = new T[powerOfTwoCapacity];
_mask = powerOfTwoCapacity - 1;
} }
/// <summary> /// <summary>
@@ -64,7 +65,7 @@ public class SPMCQueue<T>
{ {
var tail = _tail - 1; var tail = _tail - 1;
Volatile.Write(ref _tail, tail); Volatile.Write(ref _tail, tail);
Interlocked.MemoryBarrier(); Interlocked.MemoryBarrier();
var head = Volatile.Read(ref _head); var head = Volatile.Read(ref _head);

View File

@@ -84,6 +84,11 @@ internal class WorkerThread : IDisposable
{ {
return true; return true;
} }
}
for (var offset = 0; offset < helperThreadCount; offset++)
{
var p = cascade[index + offset];
for (var i = 1; i < _scheduler.WorkerCount; i++) for (var i = 1; i < _scheduler.WorkerCount; i++)
{ {

View File

@@ -143,6 +143,8 @@ public readonly unsafe struct AllocationHandle
private readonly ReallocFunc _realloc; private readonly ReallocFunc _realloc;
private readonly FreeFunc _free; private readonly FreeFunc _free;
public bool IsValid => _alloc != null && _realloc != null && _free != null;
public AllocationHandle(void* state, AllocFunc alloc, ReallocFunc realloc, FreeFunc free) public AllocationHandle(void* state, AllocFunc alloc, ReallocFunc realloc, FreeFunc free)
{ {
_state = state; _state = state;

View File

@@ -23,10 +23,12 @@ public unsafe struct VirtualMemoryBlock : IDisposable
} }
var addr = _baseAddress; var addr = _baseAddress;
var size = _size;
_baseAddress = null; _baseAddress = null;
_size = 0; _size = 0;
_committed = 0; _committed = 0;
MemoryUtility.Munmap(addr, _size); MemoryUtility.Munmap(addr, size);
} }
} }

View File

@@ -77,4 +77,22 @@ public interface IUnsafeHashCollection<T> : IDisposable
/// <param name="newSize">Specifies the new size to which the collection should be adjusted.</param> /// <param name="newSize">Specifies the new size to which the collection should be adjusted.</param>
/// <param name="option">Specifies allocation options that may affect how memory is managed during the resize operation.</param> /// <param name="option">Specifies allocation options that may affect how memory is managed during the resize operation.</param>
void Resize(int newSize, AllocationOption option); void Resize(int newSize, AllocationOption option);
}
public interface IUnsafeBitSet
{
int Count
{
get;
}
int NextSetBit(int startIndex);
void SetBit(int index);
void ClearBit(int index);
bool IsSet(int index);
void SetAll();
void ClearAll();
Span<uint> AsSpan();
} }

View File

@@ -1,4 +1,5 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
@@ -33,7 +34,7 @@ internal class UnsafeBitSetDebugView
} }
[DebuggerTypeProxy(typeof(UnsafeBitSetDebugView))] [DebuggerTypeProxy(typeof(UnsafeBitSetDebugView))]
public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet> public unsafe struct UnsafeBitSet : IUnsafeBitSet, IDisposable, IEquatable<UnsafeBitSet>
{ {
public ref struct Iterator public ref struct Iterator
{ {
@@ -54,9 +55,9 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
} }
} }
internal const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31 internal const int BIT_SIZE = sizeof(uint) * 8 - 1; // 31
internal const int _INDEX_SIZE = 5; // log_2(BitSize + 1) internal const int INDEX_SIZE = 5; // log_2(BitSize + 1)
internal const int _MASK = (1 << 5) - 1; // 0x1F, the mask to get the bit index inside a uint internal const int MASK = (1 << 5) - 1; // 0x1F, the mask to get the bit index inside a uint
internal static readonly int s_padding = Vector<uint>.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically internal static readonly int s_padding = Vector<uint>.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically
private UnsafeArray<uint> _bits; private UnsafeArray<uint> _bits;
@@ -76,7 +77,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
/// <summary> /// <summary>
/// Gets the total number of bits represented by the current instance. /// Gets the total number of bits represented by the current instance.
/// </summary> /// </summary>
public readonly int Count => _bits.Count << _INDEX_SIZE; public readonly int Count => _bits.Count << INDEX_SIZE;
public readonly bool IsCreated => _bits.IsCreated; public readonly bool IsCreated => _bits.IsCreated;
@@ -96,7 +97,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
/// <param name="option">The allocation option.</param> /// <param name="option">The allocation option.</param>
public UnsafeBitSet(int minimalLength, AllocationHandle handle, AllocationOption option = AllocationOption.None) public UnsafeBitSet(int minimalLength, AllocationHandle handle, AllocationOption option = AllocationOption.None)
{ {
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 = new UnsafeArray<uint>(length, handle, option); _bits = new UnsafeArray<uint>(length, handle, option);
@@ -113,12 +114,12 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
_bits.CopyFrom(bits); _bits.CopyFrom(bits);
_highestBit = 0; _highestBit = 0;
_max = _bits.Count * (_BIT_SIZE + 1) - 1; // Calculate the maximum index in use _max = _bits.Count * (BIT_SIZE + 1) - 1; // Calculate the maximum index in use
for (var i = 0; i < _bits.Count; i++) for (var i = 0; i < _bits.Count; i++)
{ {
if (_bits[i] != 0) if (_bits[i] != 0)
{ {
_highestBit = Math.Max(_highestBit, i * (_BIT_SIZE + 1) + BitOperations.Log2(_bits[i]) + 1); _highestBit = Math.Max(_highestBit, i * (BIT_SIZE + 1) + BitOperations.Log2(_bits[i]) + 1);
} }
} }
} }
@@ -137,7 +138,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RequiredLength(int id) public static int RequiredLength(int id)
{ {
return (id >> _INDEX_SIZE) + int.Sign(id & _BIT_SIZE); return (id >> INDEX_SIZE) + int.Sign(id & BIT_SIZE);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -155,13 +156,13 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly 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)
{ {
return false; return false;
} }
return (_bits[b] & 1 << (index & _BIT_SIZE)) != 0; return (_bits[b] & 1 << (index & BIT_SIZE)) != 0;
} }
/// <summary> /// <summary>
@@ -172,7 +173,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetBit(int index) public void SetBit(int index)
{ {
var b = index >> _INDEX_SIZE; var b = index >> INDEX_SIZE;
if (b >= _bits.Count) if (b >= _bits.Count)
{ {
_bits.Resize(index); _bits.Resize(index);
@@ -181,8 +182,8 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
// 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;
_bits[b] |= 1u << (index & _BIT_SIZE); _bits[b] |= 1u << (index & BIT_SIZE);
} }
/// <summary> /// <summary>
@@ -192,13 +193,13 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ClearBit(int index) public void ClearBit(int index)
{ {
var b = index >> _INDEX_SIZE; var b = index >> INDEX_SIZE;
if (b >= _bits.Count) if (b >= _bits.Count)
{ {
return; return;
} }
_bits[b] &= ~(1u << (index & _BIT_SIZE)); _bits[b] &= ~(1u << (index & BIT_SIZE));
} }
/// <summary> /// <summary>
@@ -209,8 +210,8 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
{ {
_bits.AsSpan().Fill(0xffffffff); _bits.AsSpan().Fill(0xffffffff);
_highestBit = _bits.Count * (_BIT_SIZE + 1) - 1; _highestBit = _bits.Count * (BIT_SIZE + 1) - 1;
_max = _highestBit / (_BIT_SIZE + 1) + 1; _max = _highestBit / (BIT_SIZE + 1) + 1;
} }
/// <summary> /// <summary>
@@ -229,21 +230,21 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
/// </summary> /// </summary>
public readonly int NextSetBit(int startIndex) public readonly int NextSetBit(int startIndex)
{ {
var wordIndex = startIndex >> _BIT_SIZE; var wordIndex = startIndex >> BIT_SIZE;
if (wordIndex >= _bits.Count) if (wordIndex >= _bits.Count)
{ {
return -1; return -1;
} }
// Mask off bits below startIndex in the first word: // Mask off bits below startIndex in the first word:
var word = _bits[wordIndex] & ~0u << (startIndex & _MASK); var word = _bits[wordIndex] & ~0u << (startIndex & MASK);
while (true) while (true)
{ {
if (word != 0) if (word != 0)
{ {
// get the least-significant set bit // get the least-significant set bit
var bit = BitOperations.TrailingZeroCount(word); var bit = BitOperations.TrailingZeroCount(word);
return (wordIndex << _BIT_SIZE) + bit; return (wordIndex << BIT_SIZE) + bit;
} }
wordIndex++; wordIndex++;
@@ -259,7 +260,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
public void Resize(int minimalLength, AllocationOption option = AllocationOption.None) public void Resize(int minimalLength, AllocationOption option = AllocationOption.None)
{ {
var oldSize = _bits.Count; 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);
@@ -712,7 +713,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
/// <returns>The <see cref="Span{T}"/>.</returns> /// <returns>The <see cref="Span{T}"/>.</returns>
public readonly Span<uint> AsSpan() public readonly Span<uint> AsSpan()
{ {
var max = _highestBit / (_BIT_SIZE + 1) + 1; var max = _highestBit / (BIT_SIZE + 1) + 1;
return _bits.AsSpan()[..max]; return _bits.AsSpan()[..max];
} }
@@ -824,7 +825,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
/// represents a non resizable collection of bits. /// represents a non resizable collection of bits.
/// Used to set, check and clear bits on a allocated <see cref="UnsafeBitSet"/> or on the stack. /// Used to set, check and clear bits on a allocated <see cref="UnsafeBitSet"/> or on the stack.
/// </summary> /// </summary>
public readonly ref struct SpanBitSet : IEquatable<SpanBitSet> public readonly ref struct SpanBitSet : IUnsafeBitSet, IEquatable<SpanBitSet>
{ {
public ref struct Iterator public ref struct Iterator
{ {
@@ -845,15 +846,17 @@ public readonly ref struct SpanBitSet : IEquatable<SpanBitSet>
} }
} }
private const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31 private const int BIT_SIZE = sizeof(uint) * 8 - 1; // 31
// NOTE: Is a byte not 8 bits? // NOTE: Is a byte not 8 bits?
private const int _BYTE_SIZE = 5; // log_2(BitSize + 1) private const int BYTE_SIZE = 5; // log_2(BitSize + 1)
/// <summary> /// <summary>
/// The bits from the bitset. /// The bits from the bitset.
/// </summary> /// </summary>
private readonly Span<uint> _bits; private readonly Span<uint> _bits;
public int Count => _bits.Length << BYTE_SIZE;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UnsafeBitSet" /> class. /// Initializes a new instance of the <see cref="UnsafeBitSet" /> class.
/// </summary> /// </summary>
@@ -874,13 +877,13 @@ public readonly ref struct SpanBitSet : IEquatable<SpanBitSet>
/// <returns>True if it is, otherwise false</returns> /// <returns>True if it is, otherwise false</returns>
public bool IsSet(int index) public bool IsSet(int index)
{ {
var b = index >> _BYTE_SIZE; var b = index >> BYTE_SIZE;
if (b >= _bits.Length) if (b >= _bits.Length)
{ {
return false; return false;
} }
return (_bits[b] & 1 << (index & _BIT_SIZE)) != 0; return (_bits[b] & 1 << (index & BIT_SIZE)) != 0;
} }
/// <summary> /// <summary>
@@ -890,13 +893,13 @@ public readonly ref struct SpanBitSet : IEquatable<SpanBitSet>
/// <param name="index">The index.</param> /// <param name="index">The index.</param>
public void SetBit(int index) public void SetBit(int index)
{ {
var b = index >> _BYTE_SIZE; var b = index >> BYTE_SIZE;
if (b >= _bits.Length) if (b >= _bits.Length)
{ {
return; return;
} }
_bits[b] |= 1u << (index & _BIT_SIZE); _bits[b] |= 1u << (index & BIT_SIZE);
} }
/// <summary> /// <summary>
@@ -905,13 +908,13 @@ public readonly ref struct SpanBitSet : IEquatable<SpanBitSet>
/// <param name="index">The index.</param> /// <param name="index">The index.</param>
public void ClearBit(int index) public void ClearBit(int index)
{ {
var b = index >> _BYTE_SIZE; var b = index >> BYTE_SIZE;
if (b >= _bits.Length) if (b >= _bits.Length)
{ {
return; return;
} }
_bits[b] &= ~(1u << (index & _BIT_SIZE)); _bits[b] &= ~(1u << (index & BIT_SIZE));
} }
/// <summary> /// <summary>
@@ -936,21 +939,21 @@ public readonly ref struct SpanBitSet : IEquatable<SpanBitSet>
public int NextSetBit(int startIndex) public int NextSetBit(int startIndex)
{ {
var wordIndex = startIndex >> _BIT_SIZE; var wordIndex = startIndex >> BIT_SIZE;
if (wordIndex >= _bits.Length) if (wordIndex >= _bits.Length)
{ {
return -1; return -1;
} }
// Mask off bits below startIndex in the first word: // Mask off bits below startIndex in the first word:
var word = _bits[wordIndex] & ~0u << (startIndex & UnsafeBitSet._MASK); var word = _bits[wordIndex] & ~0u << (startIndex & UnsafeBitSet.MASK);
while (true) while (true)
{ {
if (word != 0) if (word != 0)
{ {
// get the least-significant set bit // get the least-significant set bit
var bit = BitOperations.TrailingZeroCount(word); var bit = BitOperations.TrailingZeroCount(word);
return (wordIndex << _BIT_SIZE) + bit; return (wordIndex << BIT_SIZE) + bit;
} }
wordIndex++; wordIndex++;

View File

@@ -0,0 +1,101 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Collections;
public unsafe struct UnsafeString : IDisposable
{
private UnsafeArray<char> _chars;
public readonly int Length => _chars.Length;
public readonly char this[int index] => _chars[index];
public UnsafeString(ReadOnlySpan<char> span, AllocationHandle handle)
{
_chars = new UnsafeArray<char>(span.Length, handle);
span.CopyTo(_chars.AsSpan());
}
public UnsafeString(UnsafeString other)
{
_chars = new UnsafeArray<char>(other.Length, other._chars.AllocationHandle);
other.AsSpan().CopyTo(_chars.AsSpan());
}
public readonly UnsafeString Copy(AllocationHandle handle = default)
{
handle = handle.IsValid ? handle : _chars.AllocationHandle;
var clone = new UnsafeString(AsSpan(), handle);
return clone;
}
public readonly ReadOnlySpan<char> AsSpan()
{
return _chars.AsSpan();
}
public readonly void* GetUnsafePtr()
{
return _chars.GetUnsafePtr();
}
public readonly override string ToString()
{
return new string(_chars.AsSpan());
}
public void Dispose()
{
_chars.Dispose();
}
}
public unsafe struct UnsafeText : IDisposable
{
private UnsafeArray<byte> _chars;
public readonly int Length => _chars.Length;
public readonly byte this[int index] => _chars[index];
public UnsafeText(ReadOnlySpan<byte> span, AllocationHandle handle)
{
_chars = new UnsafeArray<byte>(span.Length, handle);
span.CopyTo(_chars.AsSpan());
}
public UnsafeText(UnsafeText other)
{
_chars = new UnsafeArray<byte>(other.Length, other._chars.AllocationHandle);
other.AsSpan().CopyTo(_chars.AsSpan());
}
public readonly UnsafeText Copy(AllocationHandle handle = default)
{
handle = handle.IsValid ? handle : _chars.AllocationHandle;
var clone = new UnsafeText(AsSpan(), handle);
return clone;
}
public readonly ReadOnlySpan<byte> AsSpan()
{
return _chars.AsSpan();
}
public readonly void* GetUnsafePtr()
{
return _chars.GetUnsafePtr();
}
public readonly override string ToString()
{
return Encoding.UTF8.GetString(_chars.AsSpan());
}
public void Dispose()
{
_chars.Dispose();
}
}

View File

@@ -7,12 +7,19 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<AssemblyVersion>1.7.3</AssemblyVersion> <AssemblyVersion>1.7.4</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl> <PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl> <RepositoryUrl>https://github.com/misakieku/Misaki.HighPerformance.git</RepositoryUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>native;unsafe;memory;allocator;mmap;arena;tlsf;freelist;high-performance;dod;ecs;native-collections;native-array;game-engine</PackageTags>
<Description>
A true high-performance, low-level native memory and collection library for C#.
Features a pluggable memory allocation architecture (AllocationHandle) with built-in allocators: Arena, FreeList, TLSF, and Malloc (mimalloc support).
Includes fully unmanaged native collections (UnsafeArray, UnsafeList, UnsafeHashMap, etc.) and cross-platform mmap/munmap wrappers.
Designed for Data-Oriented Design (DOD), custom game engines, and zero-allocation systems.
</Description>
<IncludeBuildOutput>false</IncludeBuildOutput> <IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>contentFiles</ContentTargetFolders> <ContentTargetFolders>contentFiles</ContentTargetFolders>
</PropertyGroup> </PropertyGroup>

View File

@@ -172,7 +172,7 @@ internal unsafe struct GGXMipGenerationJobSPMD : IJobSPMD<float, int>
} }
} }
internal unsafe struct GGXMipGenerationJobSPMD<TFloat, TInt> : IJobParallelFor internal unsafe struct GGXMipGenerationJobSPMD<TFloat, TInt> : IJobParallel
where TFloat : unmanaged, ISPMDLane<TFloat, float> where TFloat : unmanaged, ISPMDLane<TFloat, float>
where TInt : unmanaged, ISPMDLane<TInt, int> where TInt : unmanaged, ISPMDLane<TInt, int>
{ {
@@ -249,21 +249,12 @@ internal unsafe struct GGXMipGenerationJobSPMD<TFloat, TInt> : IJobParallelFor
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) private readonly void ProcessPixel(int local_i, MipLevel* pLevel)
{ {
var m = 0;
while (m < numMipLevels - 1 && loopIndex >= pMipLevels[m + 1].offset)
{
m++;
}
var pLevel = &pMipLevels[m];
var w = (int)pLevel->width; var w = (int)pLevel->width;
var h = (int)pLevel->height; var h = (int)pLevel->height;
var pData = pLevel->data; var pData = pLevel->data;
var local_i = loopIndex - pLevel->offset;
var x = local_i % w; var x = local_i % w;
var y = local_i / w; var y = local_i / w;
var u = (float)x / (w - 1); var u = (float)x / (w - 1);
@@ -358,6 +349,33 @@ internal unsafe struct GGXMipGenerationJobSPMD<TFloat, TInt> : IJobParallelFor
pData[out_idx + 1] = prefilteredColor.y; pData[out_idx + 1] = prefilteredColor.y;
pData[out_idx + 2] = prefilteredColor.z; pData[out_idx + 2] = prefilteredColor.z;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{
var m = 0;
while (m < numMipLevels - 1 && startIndex >= pMipLevels[m + 1].offset)
{
m++;
}
var i = startIndex;
while (i < endIndex)
{
var pLevel = &pMipLevels[m];
var next = m + 1 < numMipLevels ? pMipLevels[m + 1].offset : int.MaxValue;
var stop = Math.Min(endIndex, next);
for (; i < stop; i++)
{
var local_i = i - pLevel->offset;
ProcessPixel(local_i, pLevel);
}
m++;
}
}
} }
[SimpleJob(RunStrategy.ColdStart, launchCount: 1, warmupCount: 0, iterationCount: 1, invocationCount: 1, id: "QuickRun")] [SimpleJob(RunStrategy.ColdStart, launchCount: 1, warmupCount: 0, iterationCount: 1, invocationCount: 1, id: "QuickRun")]
@@ -388,8 +406,8 @@ public unsafe class GGXMipGenerationBenchmark
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
//const string imagePath = "F:\\c\\SimpleRayTracer\\native\\assets\\hdri\\golden_gate_hills_1k.hdr"; const string imagePath = "F:\\c\\SimpleRayTracer\\native\\assets\\hdri\\golden_gate_hills_1k.hdr";
const string imagePath = "C:\\Users\\Misaki\\Downloads\\grasslands_sunset_4k.hdr"; //const string imagePath = "C:\\Users\\Misaki\\Downloads\\grasslands_sunset_4k.hdr";
using var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); using var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read);
_image = ImageResultFloat.FromStream(stream, ColorComponents.RGB); _image = ImageResultFloat.FromStream(stream, ColorComponents.RGB);
@@ -517,7 +535,7 @@ public unsafe class GGXMipGenerationBenchmark
radicalInverse_VdCLut = _radicalInverse_VdCLut radicalInverse_VdCLut = _radicalInverse_VdCLut
}; };
handle = _jobScheduler.ScheduleParallelFor(in job, _totalPixel, 64); handle = _jobScheduler.ScheduleParallel(in job, _totalPixel, 64);
} }
else else
{ {
@@ -529,7 +547,7 @@ public unsafe class GGXMipGenerationBenchmark
radicalInverse_VdCLut = _radicalInverse_VdCLut radicalInverse_VdCLut = _radicalInverse_VdCLut
}; };
handle = _jobScheduler.ScheduleParallelFor(in job, _totalPixel, 64); handle = _jobScheduler.ScheduleParallel(in job, _totalPixel, 64);
} }
_jobScheduler.Wait(handle); _jobScheduler.Wait(handle);