From 1386f9150ca3b2db5a97c3e0423f5e6b710a8b37 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Fri, 7 Jul 2023 13:01:17 +0200 Subject: [PATCH] Fix concurrency deadlock I'm stupid and forgot it divided the number of iterations by 8, no wonder it was so fast lmao --- Benchmark/Program.cs | 8 ++--- Solver/Crafty/ActionSet.cs | 3 +- Solver/Crafty/ArenaBuffer.cs | 60 +++++++++++++++++++++++++++++++ Solver/Crafty/ArenaNode.cs | 59 +----------------------------- Solver/Crafty/SolverConcurrent.cs | 4 +-- Solver/Crafty/SolverSingle.cs | 2 +- Solver/Crafty/SolverUtils.cs | 6 ++-- 7 files changed, 71 insertions(+), 71 deletions(-) create mode 100644 Solver/Crafty/ArenaBuffer.cs diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index aea78aa..ef2ee2d 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -41,15 +41,13 @@ internal static class Program QualityDivider = 115, ProgressModifier = 80, ProgressDivider = 130, - }, - 0 + } ); - var threads = 8; var config = new SolverConfig() { - Iterations = 30_000 / threads, - ThreadCount = threads, + Iterations = 1_000_000, + ThreadCount = 8, }; Debugger.Break(); diff --git a/Solver/Crafty/ActionSet.cs b/Solver/Crafty/ActionSet.cs index 30632c2..8d0a49c 100644 --- a/Solver/Crafty/ActionSet.cs +++ b/Solver/Crafty/ActionSet.cs @@ -71,7 +71,7 @@ public struct ActionSet public readonly bool IsEmpty => bits == 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ActionType SelectRandom(Random random) => ElementAt(0);// random.Next(Count)); + public readonly ActionType SelectRandom(Random random) => ElementAt(random.Next(Count)); [MethodImpl(MethodImplOptions.AggressiveInlining)] public ActionType? PopRandomConcurrent(Random random) @@ -117,7 +117,6 @@ public struct ActionSet [MethodImpl(MethodImplOptions.AggressiveInlining)] public ActionType PopRandom(Random random) { - return PopFirst(); var action = ElementAt(random.Next(Count)); RemoveAction(action); return action; diff --git a/Solver/Crafty/ArenaBuffer.cs b/Solver/Crafty/ArenaBuffer.cs new file mode 100644 index 0000000..ccd7709 --- /dev/null +++ b/Solver/Crafty/ArenaBuffer.cs @@ -0,0 +1,60 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Craftimizer.Solver.Crafty; + +// Adapted from https://github.com/dtao/ConcurrentList/blob/4fcf1c76e93021a41af5abb2d61a63caeba2adad/ConcurrentList/ConcurrentList.cs +public struct ArenaBuffer +{ + // Technically 25, but it's very unlikely to actually get to there. + // The benchmark reaches 20 at most, but here we have a little leeway just in case. + private const int MaxSize = 24; + + private static int BatchSize = Vector.Count; + private static int BatchSizeBits = int.Log2(BatchSize); + private static int BatchSizeMask = BatchSize - 1; + + private static int BatchCount = MaxSize / BatchSize; + + public T[][] Data; + private int index; // Unused in single threaded workload + private int count; + + public readonly int Count => count; + + public void AddConcurrent(T node) + { + if (Data == null) + Interlocked.CompareExchange(ref Data, new T[BatchCount][], null); + + var idx = Interlocked.Increment(ref index) - 1; + + var (arrayIdx, subIdx) = GetArrayIndex(idx); + + if (Data[arrayIdx] == null) + Interlocked.CompareExchange(ref Data[arrayIdx], new T[BatchSize], null); + + Data[arrayIdx][subIdx] = node; + + Interlocked.Increment(ref count); + } + + public void Add(T node) + { + Data ??= new T[BatchCount][]; + + var idx = count++; + + var (arrayIdx, subIdx) = GetArrayIndex(idx); + + Data[arrayIdx] ??= new T[BatchSize]; + + Data[arrayIdx][subIdx] = node; + } + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static (int arrayIdx, int subIdx) GetArrayIndex(int idx) => + (idx >> BatchSizeBits, idx & BatchSizeMask); +} diff --git a/Solver/Crafty/ArenaNode.cs b/Solver/Crafty/ArenaNode.cs index 79d4b36..560ebaf 100644 --- a/Solver/Crafty/ArenaNode.cs +++ b/Solver/Crafty/ArenaNode.cs @@ -1,68 +1,11 @@ -using System.Diagnostics.Contracts; -using System.Numerics; using System.Runtime.CompilerServices; namespace Craftimizer.Solver.Crafty; public sealed class ArenaNode where T : struct { - // Adapted from https://github.com/dtao/ConcurrentList/blob/4fcf1c76e93021a41af5abb2d61a63caeba2adad/ConcurrentList/ConcurrentList.cs - public struct ChildBuffer - { - // Technically 25, but it's very unlikely to actually get to there. - // The benchmark reaches 20 at most, but here we have a little leeway just in case. - private const int MaxSize = 24; - - private static int BatchSize = Vector.Count; - private static int BatchSizeBits = int.Log2(BatchSize); - private static int BatchSizeMask = BatchSize - 1; - - private static int BatchCount = MaxSize / BatchSize; - - public ArenaNode[][] Data; - private int index; // Unused in single threaded workload - private int count; - - public readonly int Count => count; - - public void AddConcurrent(ArenaNode node) - { - if (Data == null) - Interlocked.CompareExchange(ref Data, new ArenaNode[BatchCount][], null); - - var idx = Interlocked.Increment(ref index) - 1; - - var (arrayIdx, subIdx) = GetArrayIndex(idx); - - if (Data[arrayIdx] == null) - Interlocked.CompareExchange(ref Data[arrayIdx], new ArenaNode[BatchSize], null); - - Data[arrayIdx][subIdx] = node; - - Interlocked.Increment(ref count); - } - - public void Add(ArenaNode node) - { - Data ??= new ArenaNode[BatchCount][]; - - var idx = count++; - - var (arrayIdx, subIdx) = GetArrayIndex(idx); - - Data[arrayIdx] ??= new ArenaNode[BatchSize]; - - Data[arrayIdx][subIdx] = node; - } - - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static (int arrayIdx, int subIdx) GetArrayIndex(int idx) => - (idx >> BatchSizeBits, idx & BatchSizeMask); - } - public T State; - public ChildBuffer Children; + public ArenaBuffer> Children; public readonly ArenaNode? Parent; public ArenaNode(T state, ArenaNode? parent = null) diff --git a/Solver/Crafty/SolverConcurrent.cs b/Solver/Crafty/SolverConcurrent.cs index 2a78649..a258d9c 100644 --- a/Solver/Crafty/SolverConcurrent.cs +++ b/Solver/Crafty/SolverConcurrent.cs @@ -53,7 +53,7 @@ public sealed class SolverConcurrent : ISolver var poppedAction = initialState.AvailableActions.PopRandomConcurrent(random); if (!poppedAction.HasValue) return null; - var expandedNode = initialNode.Add(SolverUtils.Execute(simulator, initialState.State, poppedAction.Value, true)); + var expandedNode = initialNode.AddConcurrent(SolverUtils.Execute(simulator, initialState.State, poppedAction.Value, true)); return SolverUtils.Rollout(ref config, rootNode, expandedNode, random, simulator); } @@ -84,7 +84,7 @@ public sealed class SolverConcurrent : ISolver } public static void SearchThread(SolverConfig config, Node rootNode, CancellationToken token) => - SolverUtils.Search(ref config, rootNode, token); + SolverUtils.Search(ref config, config.Iterations / config.ThreadCount, rootNode, token); public static void Search(ref SolverConfig config, Node rootNode, CancellationToken token) { diff --git a/Solver/Crafty/SolverSingle.cs b/Solver/Crafty/SolverSingle.cs index b7c9aed..a22fa56 100644 --- a/Solver/Crafty/SolverSingle.cs +++ b/Solver/Crafty/SolverSingle.cs @@ -75,5 +75,5 @@ public sealed class SolverSingle : ISolver } public static void Search(ref SolverConfig config, Node rootNode, CancellationToken token) => - SolverUtils.Search(ref config, rootNode, token); + SolverUtils.Search(ref config, config.Iterations, rootNode, token); } diff --git a/Solver/Crafty/SolverUtils.cs b/Solver/Crafty/SolverUtils.cs index 6b7cbb4..a08d525 100644 --- a/Solver/Crafty/SolverUtils.cs +++ b/Solver/Crafty/SolverUtils.cs @@ -29,7 +29,7 @@ public static class SolverUtils if (!state.AvailableActions.HasAction(action)) return (startNode, CompletionState.InvalidAction); - state.AvailableActions.RemoveActionConcurrent(action); + state.AvailableActions.RemoveAction(action); startNode = startNode.Add(Execute(simulator, state.State, action, strict)); } @@ -167,11 +167,11 @@ public static class SolverUtils } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Search(ref SolverConfig config, Node rootNode, CancellationToken token) where S : ISolver + public static void Search(ref SolverConfig config, int iterations, Node rootNode, CancellationToken token) where S : ISolver { Simulator simulator = new(rootNode.State.State, config.MaxStepCount); var random = rootNode.State.State.Input.Random; - for (var i = 0; i < config.Iterations; i++) + for (var i = 0; i < iterations; i++) { if (token.IsCancellationRequested) break;