Compare commits
4 Commits
e9d3e695ba
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 53bba54d43 | |||
| 7c9612ceb0 | |||
| fef20f05b7 | |||
| 27569e9eb5 |
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,3 +78,21 @@ public interface IUnsafeHashCollection<T> : IDisposable
|
|||||||
/// <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();
|
||||||
|
}
|
||||||
@@ -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++;
|
||||||
|
|||||||
101
src/Misaki.HighPerformance.LowLevel/Collections/UnsafeString.cs
Normal file
101
src/Misaki.HighPerformance.LowLevel/Collections/UnsafeString.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user