Job system priorities, async waits, parallel map/queue
Major refactor: - Add job priority tiers and async wait APIs to IJobScheduler - Implement priority-based job queues and scheduling logic - Introduce UnsafeParallelHashMap and refactor UnsafeParallelQueue - Refactor UnsafeSlotMap to chunked storage for scalability - Update SlotMap/ConcurrentSlotMap for consistency and perf - Add new benchmarks and unit tests for parallel collections - Misc: add MemoryUtility.AlignUp, version bumps, test improvements, bug fixes
This commit is contained in:
@@ -15,7 +15,7 @@ public class TestUnsafeChunkedQueue
|
||||
[TestMethod]
|
||||
public void BasicEnqueueDequeueTest()
|
||||
{
|
||||
using var queue = new UnsafeChunkedQueue<int>(32, AllocationHandle.Persistent);
|
||||
using var queue = new UnsafeParallelQueue<int>(32, AllocationHandle.Persistent);
|
||||
|
||||
Assert.IsTrue(queue.IsCreated);
|
||||
|
||||
@@ -35,7 +35,7 @@ public class TestUnsafeChunkedQueue
|
||||
public void ChunkExpansionTest()
|
||||
{
|
||||
// Force chunk expansions by enqueuing more than the chunk capacity
|
||||
using var queue = new UnsafeChunkedQueue<int>(16, AllocationHandle.Persistent);
|
||||
using var queue = new UnsafeParallelQueue<int>(16, AllocationHandle.Persistent);
|
||||
|
||||
var totalItems = 100;
|
||||
|
||||
@@ -57,7 +57,7 @@ public class TestUnsafeChunkedQueue
|
||||
public void ConcurrentEnqueueDequeueTest()
|
||||
{
|
||||
// Multi-threaded stress test to verify lock-free safety and chunk caching
|
||||
using var queue = new UnsafeChunkedQueue<int>(64, AllocationHandle.Persistent);
|
||||
using var queue = new UnsafeParallelQueue<int>(64, AllocationHandle.Persistent);
|
||||
var totalElements = 100_000;
|
||||
|
||||
var enqueueTask = Task.Run(() =>
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
||||
|
||||
[TestClass]
|
||||
public class TestUnsafeParallelHashMap
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestParallelWrite()
|
||||
{
|
||||
using var map = new UnsafeParallelHashMap<int, int>(10000, 1, AllocationHandle.Temp, AllocationOption.None);
|
||||
var writer = map.AsParallelWriter();
|
||||
|
||||
Parallel.For(0, 10000, i =>
|
||||
{
|
||||
writer.TryAdd(i, i * 2);
|
||||
});
|
||||
|
||||
Assert.AreEqual(10000, map.Count);
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
Assert.IsTrue(map.TryGetValue(i, out var val));
|
||||
Assert.AreEqual(i * 2, val);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestBasicOperations()
|
||||
{
|
||||
using var map = new UnsafeParallelHashMap<int, int>(16, 1, AllocationHandle.Temp, AllocationOption.None);
|
||||
|
||||
Assert.IsTrue(map.IsEmpty);
|
||||
Assert.AreEqual(0, map.Count);
|
||||
|
||||
// Add
|
||||
map.Add(1, 10);
|
||||
map.Add(2, 20);
|
||||
map.Add(3, 30);
|
||||
|
||||
Assert.IsFalse(map.IsEmpty);
|
||||
Assert.AreEqual(3, map.Count);
|
||||
|
||||
// TryGetValue existing
|
||||
Assert.IsTrue(map.TryGetValue(2, out var val));
|
||||
Assert.AreEqual(20, val);
|
||||
|
||||
// TryGetValue non-existing
|
||||
Assert.IsFalse(map.TryGetValue(4, out _));
|
||||
|
||||
// Remove existing
|
||||
Assert.IsTrue(map.Remove(2));
|
||||
Assert.AreEqual(2, map.Count);
|
||||
Assert.IsFalse(map.TryGetValue(2, out _));
|
||||
|
||||
// Remove non-existing
|
||||
Assert.IsFalse(map.Remove(4));
|
||||
|
||||
// Clear
|
||||
map.Clear();
|
||||
Assert.AreEqual(0, map.Count);
|
||||
Assert.IsTrue(map.IsEmpty);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestResize()
|
||||
{
|
||||
using var map = new UnsafeParallelHashMap<int, int>(16, 1, AllocationHandle.Temp, AllocationOption.None);
|
||||
|
||||
// Single thread adds causing resize
|
||||
for (var i = 0; i < 1000; i++)
|
||||
{
|
||||
map.Add(i, i * 10);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1000, map.Count);
|
||||
Assert.IsTrue(map.Capacity >= 1000);
|
||||
|
||||
for (var i = 0; i < 1000; i++)
|
||||
{
|
||||
Assert.IsTrue(map.TryGetValue(i, out var val));
|
||||
Assert.AreEqual(i * 10, val);
|
||||
}
|
||||
}
|
||||
|
||||
private struct BadKey : IEquatable<BadKey>
|
||||
{
|
||||
public int Id;
|
||||
public BadKey(int id) => Id = id;
|
||||
public bool Equals(BadKey other) => Id == other.Id;
|
||||
public override int GetHashCode() => 1; // Force collision
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestHashCollisions()
|
||||
{
|
||||
using var map = new UnsafeParallelHashMap<BadKey, int>(16, 1, AllocationHandle.Temp, AllocationOption.None);
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
map.Add(new BadKey(i), i * 5);
|
||||
}
|
||||
|
||||
Assert.AreEqual(10, map.Count);
|
||||
|
||||
// Verify we can retrieve them all out of the same bucket
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
Assert.IsTrue(map.TryGetValue(new BadKey(i), out var val));
|
||||
Assert.AreEqual(i * 5, val);
|
||||
}
|
||||
|
||||
// Remove from the middle of the linked list
|
||||
Assert.IsTrue(map.Remove(new BadKey(5)));
|
||||
Assert.IsFalse(map.TryGetValue(new BadKey(5), out _));
|
||||
Assert.AreEqual(9, map.Count);
|
||||
|
||||
// Make sure everything else is intact
|
||||
Assert.IsTrue(map.TryGetValue(new BadKey(6), out var val6));
|
||||
Assert.AreEqual(6 * 5, val6);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestAddDuplicate()
|
||||
{
|
||||
using var map = new UnsafeParallelHashMap<int, int>(16, 1, AllocationHandle.Temp, AllocationOption.None);
|
||||
|
||||
Assert.AreEqual(0, map.Add(1, 100));
|
||||
|
||||
// Adding again should return -1
|
||||
Assert.AreEqual(-1, map.Add(1, 200));
|
||||
Assert.AreEqual(1, map.Count);
|
||||
|
||||
var writer = map.AsParallelWriter();
|
||||
Assert.IsFalse(writer.TryAdd(1, 300));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestParallelWriteExceedsCapacity()
|
||||
{
|
||||
using var map = new UnsafeParallelHashMap<int, int>(50, 1, AllocationHandle.Temp, AllocationOption.None);
|
||||
var writer = map.AsParallelWriter();
|
||||
|
||||
// The exact exception will be wrapped in AggregateException by Parallel.For
|
||||
Assert.ThrowsExactly<AggregateException>(() =>
|
||||
{
|
||||
Parallel.For(0, 100, i =>
|
||||
{
|
||||
writer.TryAdd(i, i);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,23 @@ public class TestUnsafeSlotMap
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReferenceValidAfterResize()
|
||||
{
|
||||
var id = _slotMap.Add(10, out var gen);
|
||||
ref var value = ref _slotMap.GetElementReferenceAt(id, gen, out _);
|
||||
|
||||
Assert.AreEqual(10, value);
|
||||
|
||||
// Force resize
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
_slotMap.Add(i, out _);
|
||||
}
|
||||
|
||||
Assert.AreEqual(10, value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Clear()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user