From 3edb156d978fb2192e70d742024a3fa85f1b835c Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Fri, 10 Nov 2023 16:29:35 -0800 Subject: [PATCH 1/5] ReadOnlySpan optimizations --- Solver/ActionSet.cs | 2 +- Solver/Craftimizer.Solver.csproj | 6 ++++++ Test/Solver/ActionSet.cs | 2 -- Test/Usings.cs | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Solver/ActionSet.cs b/Solver/ActionSet.cs index 24b135d..b7cc97d 100644 --- a/Solver/ActionSet.cs +++ b/Solver/ActionSet.cs @@ -9,7 +9,7 @@ public struct ActionSet { private uint bits; - public static readonly ActionType[] AcceptedActions = new[] + internal static ReadOnlySpan AcceptedActions => new[] { ActionType.StandardTouchCombo, ActionType.AdvancedTouchCombo, diff --git a/Solver/Craftimizer.Solver.csproj b/Solver/Craftimizer.Solver.csproj index bf16279..eaea55b 100644 --- a/Solver/Craftimizer.Solver.csproj +++ b/Solver/Craftimizer.Solver.csproj @@ -20,6 +20,12 @@ + + + <_Parameter1>Craftimizer.Test + + + $(DefineConstants);IS_DETERMINISTIC diff --git a/Test/Solver/ActionSet.cs b/Test/Solver/ActionSet.cs index 0f4c9df..fd8c8a1 100644 --- a/Test/Solver/ActionSet.cs +++ b/Test/Solver/ActionSet.cs @@ -1,5 +1,3 @@ -using Craftimizer.Simulator.Actions; - namespace Craftimizer.Test.Solver; [TestClass] diff --git a/Test/Usings.cs b/Test/Usings.cs index da624f8..c58173e 100644 --- a/Test/Usings.cs +++ b/Test/Usings.cs @@ -1,3 +1,4 @@ global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Craftimizer.Solver; global using Craftimizer.Simulator; +global using Craftimizer.Simulator.Actions; From 036cbb2fb4e27ee79c38ddc8adec9ac8eaecf1d0 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Fri, 10 Nov 2023 18:41:58 -0800 Subject: [PATCH 2/5] Pass large structs byref instead --- Simulator/Actions/BaseComboAction.cs | 10 ++-------- Simulator/Simulator.cs | 8 ++++---- Simulator/SimulatorNoRandom.cs | 2 +- Solver/MCTS.cs | 12 ++++++------ Solver/MCTSConfig.cs | 4 ++-- Solver/SimulationNode.cs | 6 +++--- Solver/Simulator.cs | 2 +- Solver/Solver.cs | 2 +- Solver/SolverSolution.cs | 2 +- 9 files changed, 21 insertions(+), 27 deletions(-) diff --git a/Simulator/Actions/BaseComboAction.cs b/Simulator/Actions/BaseComboAction.cs index 80d11f7..16876c5 100644 --- a/Simulator/Actions/BaseComboAction.cs +++ b/Simulator/Actions/BaseComboAction.cs @@ -10,7 +10,7 @@ public abstract class BaseComboAction : BaseAction protected bool BaseCanUse(Simulator s) => base.CanUse(s); - private static bool VerifyDurability2(int durabilityA, int durability, Effects effects) + private static bool VerifyDurability2(int durabilityA, int durability, in Effects effects) { var wasteNots = effects.HasEffect(EffectType.WasteNot) || effects.HasEffect(EffectType.WasteNot2); // -A @@ -23,13 +23,10 @@ private static bool VerifyDurability2(int durabilityA, int durability, Effects e return true; } - public static bool VerifyDurability2(SimulationState s, int durabilityA) => - VerifyDurability2(durabilityA, s.Durability, s.ActiveEffects); - public static bool VerifyDurability2(Simulator s, int durabilityA) => VerifyDurability2(durabilityA, s.Durability, s.ActiveEffects); - public static bool VerifyDurability3(int durabilityA, int durabilityB, int durability, Effects effects) + public static bool VerifyDurability3(int durabilityA, int durabilityB, int durability, in Effects effects) { var wasteNots = Math.Max(effects.GetDuration(EffectType.WasteNot), effects.GetDuration(EffectType.WasteNot2)); var manips = effects.HasEffect(EffectType.Manipulation); @@ -56,7 +53,4 @@ public static bool VerifyDurability3(int durabilityA, int durabilityB, int durab public static bool VerifyDurability3(Simulator s, int durabilityA, int durabilityB) => VerifyDurability3(durabilityA, durabilityB, s.Durability, s.ActiveEffects); - - public static bool VerifyDurability3(SimulationState s, int durabilityA, int durabilityB) => - VerifyDurability3(durabilityA, durabilityB, s.Durability, s.ActiveEffects); } diff --git a/Simulator/Simulator.cs b/Simulator/Simulator.cs index 492b4db..03dbb12 100644 --- a/Simulator/Simulator.cs +++ b/Simulator/Simulator.cs @@ -35,17 +35,17 @@ public virtual CompletionState CompletionState { public IEnumerable AvailableActions => ActionUtils.AvailableActions(this); - public Simulator(SimulationState state) + public Simulator(in SimulationState state) { State = state; } - public void SetState(SimulationState state) + public void SetState(in SimulationState state) { State = state; } - public (ActionResponse Response, SimulationState NewState) Execute(SimulationState state, ActionType action) + public (ActionResponse Response, SimulationState NewState) Execute(in SimulationState state, ActionType action) { State = state; return (Execute(action), State); @@ -75,7 +75,7 @@ private ActionResponse Execute(ActionType action) return ActionResponse.UsedAction; } - public (ActionResponse Response, SimulationState NewState, int FailedActionIdx) ExecuteMultiple(SimulationState state, IEnumerable actions) + public (ActionResponse Response, SimulationState NewState, int FailedActionIdx) ExecuteMultiple(in SimulationState state, IEnumerable actions) { State = state; var i = 0; diff --git a/Simulator/SimulatorNoRandom.cs b/Simulator/SimulatorNoRandom.cs index 1f4c827..e6c4738 100644 --- a/Simulator/SimulatorNoRandom.cs +++ b/Simulator/SimulatorNoRandom.cs @@ -2,7 +2,7 @@ namespace Craftimizer.Simulator; public class SimulatorNoRandom : Simulator { - public SimulatorNoRandom(SimulationState state) : base(state) + public SimulatorNoRandom(in SimulationState state) : base(state) { } diff --git a/Solver/MCTS.cs b/Solver/MCTS.cs index 8e40a6b..76251db 100644 --- a/Solver/MCTS.cs +++ b/Solver/MCTS.cs @@ -17,7 +17,7 @@ public sealed class MCTS public float MaxScore => rootScores.MaxScore; - public MCTS(MCTSConfig config, SimulationState state) + public MCTS(in MCTSConfig config, in SimulationState state) { this.config = config; var sim = new Simulator(state, config.MaxStepCount); @@ -30,7 +30,7 @@ public MCTS(MCTSConfig config, SimulationState state) rootScores = new(); } - private static SimulationNode Execute(Simulator simulator, SimulationState state, ActionType action, bool strict) + private static SimulationNode Execute(Simulator simulator, in SimulationState state, ActionType action, bool strict) { (_, var newState) = simulator.Execute(state, action); return new( @@ -61,7 +61,7 @@ private static Node ExecuteActions(Simulator simulator, Node startNode, ReadOnly [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static (int arrayIdx, int subIdx) ChildMaxScore(ref NodeScoresBuffer scores) + private static (int arrayIdx, int subIdx) ChildMaxScore(in NodeScoresBuffer scores) { var length = scores.Count; var vecLength = Vector.Count; @@ -111,7 +111,7 @@ private static (int arrayIdx, int subIdx) EvalBestChild( float explorationConstant, float maxScoreWeightingConstant, int parentVisits, - ref NodeScoresBuffer scores) + in NodeScoresBuffer scores) { var length = scores.Count; var vecLength = Vector.Count; @@ -168,7 +168,7 @@ private Node Select() return node; // select the node with the highest score - var at = EvalBestChild(explorationConstant, maxScoreWeightingConstant, nodeVisits, ref node.ChildScores); + var at = EvalBestChild(explorationConstant, maxScoreWeightingConstant, nodeVisits, in node.ChildScores); nodeVisits = node.ChildScores.GetVisits(at); node = node.ChildAt(at)!; } @@ -320,7 +320,7 @@ public SolverSolution Solution() while (node.Children.Count != 0) { - node = node.ChildAt(ChildMaxScore(ref node.ChildScores))!; + node = node.ChildAt(ChildMaxScore(in node.ChildScores))!; if (node.State.Action != null) actions.Add(node.State.Action.Value); diff --git a/Solver/MCTSConfig.cs b/Solver/MCTSConfig.cs index a0e554d..1bf4e3d 100644 --- a/Solver/MCTSConfig.cs +++ b/Solver/MCTSConfig.cs @@ -1,4 +1,4 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace Craftimizer.Solver; @@ -21,7 +21,7 @@ public readonly record struct MCTSConfig public float ScoreCP { get; init; } public float ScoreSteps { get; init; } - public MCTSConfig(SolverConfig config) + public MCTSConfig(in SolverConfig config) { MaxStepCount = config.MaxStepCount; MaxRolloutStepCount = config.MaxRolloutStepCount; diff --git a/Solver/SimulationNode.cs b/Solver/SimulationNode.cs index a0c722b..a2b8f34 100644 --- a/Solver/SimulationNode.cs +++ b/Solver/SimulationNode.cs @@ -19,7 +19,7 @@ public struct SimulationNode public readonly bool IsComplete => CompletionState != CompletionState.Incomplete; - public SimulationNode(SimulationState state, ActionType? action, CompletionState completionState, ActionSet actions) + public SimulationNode(in SimulationState state, ActionType? action, CompletionState completionState, ActionSet actions) { State = state; Action = action; @@ -32,10 +32,10 @@ public static CompletionState GetCompletionState(CompletionState simCompletionSt CompletionState.NoMoreActions : simCompletionState; - public readonly float? CalculateScore(MCTSConfig config) => + public readonly float? CalculateScore(in MCTSConfig config) => CalculateScoreForState(State, SimulationCompletionState, config); - public static float? CalculateScoreForState(SimulationState state, CompletionState completionState, MCTSConfig config) + public static float? CalculateScoreForState(in SimulationState state, CompletionState completionState, MCTSConfig config) { if (completionState != CompletionState.ProgressComplete) return null; diff --git a/Solver/Simulator.cs b/Solver/Simulator.cs index 8edf3e4..a3a29fa 100644 --- a/Solver/Simulator.cs +++ b/Solver/Simulator.cs @@ -20,7 +20,7 @@ public override CompletionState CompletionState } } - public Simulator(SimulationState state, int maxStepCount) : base(state) + public Simulator(in SimulationState state, int maxStepCount) : base(state) { this.maxStepCount = maxStepCount; } diff --git a/Solver/Solver.cs b/Solver/Solver.cs index ca6343e..7c9efcf 100644 --- a/Solver/Solver.cs +++ b/Solver/Solver.cs @@ -35,7 +35,7 @@ public sealed class Solver : IDisposable // Always called when a new step is generated. public event NewActionDelegate? OnNewAction; - public Solver(SolverConfig config, SimulationState state) + public Solver(in SolverConfig config, in SimulationState state) { Config = config; State = state; diff --git a/Solver/SolverSolution.cs b/Solver/SolverSolution.cs index f512479..ce38fce 100644 --- a/Solver/SolverSolution.cs +++ b/Solver/SolverSolution.cs @@ -9,7 +9,7 @@ public readonly record struct SolverSolution { public readonly IEnumerable ActionEnumerable { init => actions = SanitizeCombos(value).ToList(); } public readonly SimulationState State { get; init; } - public SolverSolution(IEnumerable actions, SimulationState state) + public SolverSolution(IEnumerable actions, in SimulationState state) { ActionEnumerable = actions; State = state; From bddc977cd8c34d8bf8ebc4a3953d259da6435256 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Fri, 10 Nov 2023 18:58:34 -0800 Subject: [PATCH 3/5] Benchmark .NET 8.0, as well --- .github/workflows/build.yml | 4 +++- Benchmark/Bench.cs | 4 +++- Simulator/SimulationState.cs | 9 +++++++++ Solver/SolverConfig.cs | 4 +--- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a8dc56..e85119a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,7 +90,9 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0' + dotnet-version: | + 7.0 + 8.0 - name: Download Dalamud run: | diff --git a/Benchmark/Bench.cs b/Benchmark/Bench.cs index 2518195..d094474 100644 --- a/Benchmark/Bench.cs +++ b/Benchmark/Bench.cs @@ -1,11 +1,13 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Diagnostics.dotTrace; +using BenchmarkDotNet.Jobs; using Craftimizer.Simulator; using Craftimizer.Solver; namespace Craftimizer.Benchmark; -[SimpleJob(iterationCount: 10)] +[SimpleJob(RuntimeMoniker.Net70, baseline: true)] +[SimpleJob(RuntimeMoniker.Net80)] [MinColumn, Q1Column, Q3Column, MaxColumn] [DotTraceDiagnoser] public class Bench diff --git a/Simulator/SimulationState.cs b/Simulator/SimulationState.cs index e743ade..74cac04 100644 --- a/Simulator/SimulationState.cs +++ b/Simulator/SimulationState.cs @@ -44,4 +44,13 @@ public SimulationState(SimulationInput input) ActionCount = 0; ActionStates = new(); } + +#if IS_DETERMINISTIC + public override readonly string ToString() + { + var b = new System.Text.StringBuilder(); + PrintMembers(b); + return Convert.ToHexString(System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(b.ToString()))); + } +#endif } diff --git a/Solver/SolverConfig.cs b/Solver/SolverConfig.cs index d261949..65274a9 100644 --- a/Solver/SolverConfig.cs +++ b/Solver/SolverConfig.cs @@ -1,5 +1,3 @@ -using Craftimizer.Simulator.Actions; -using Craftimizer.Simulator; using System.Runtime.InteropServices; namespace Craftimizer.Solver; @@ -71,4 +69,4 @@ public SolverConfig() FurcatedActionCount = Environment.ProcessorCount / 2, Algorithm = SolverAlgorithm.StepwiseForked }; -} \ No newline at end of file +} From 23a229364ed75ea7a061934935b260e247e03ef7 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Sat, 11 Nov 2023 23:17:58 -0800 Subject: [PATCH 4/5] Shorted bench parameters --- Benchmark/Bench.cs | 22 ++++++++++++++++------ Benchmark/Program.cs | 8 ++++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Benchmark/Bench.cs b/Benchmark/Bench.cs index d094474..8485503 100644 --- a/Benchmark/Bench.cs +++ b/Benchmark/Bench.cs @@ -3,6 +3,8 @@ using BenchmarkDotNet.Jobs; using Craftimizer.Simulator; using Craftimizer.Solver; +using System.Security.Cryptography; +using System.Text; namespace Craftimizer.Benchmark; @@ -12,6 +14,14 @@ namespace Craftimizer.Benchmark; [DotTraceDiagnoser] public class Bench { + public record struct SHAWrapper(T Data) where T : notnull + { + public static implicit operator T(SHAWrapper wrapper) => wrapper.Data; + + public override readonly string ToString() => + Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(Data.ToString()!))); + } + private static SimulationInput[] Inputs { get; } = new SimulationInput[] { // https://craftingway.app/rotation/loud-namazu-jVe9Y // Chondrite Saw @@ -70,22 +80,22 @@ public class Bench }) }; - public static IEnumerable States => Inputs.Select(i => new SimulationState(i)); + public static IEnumerable> States => Inputs.Select(i => new SHAWrapper(new(i))); - public static IEnumerable Configs => new SolverConfig[] + public static IEnumerable> Configs => new SHAWrapper[] { - new() + new(new() { Algorithm = SolverAlgorithm.Stepwise, Iterations = 30_000, - } + }) }; [ParamsSource(nameof(States))] - public SimulationState State { get; set; } + public SHAWrapper State { get; set; } [ParamsSource(nameof(Configs))] - public SolverConfig Config { get; set; } + public SHAWrapper Config { get; set; } [Benchmark] public async Task Solve() diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index 70c6299..4ba614e 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -1,6 +1,7 @@ using Craftimizer.Simulator; using Craftimizer.Simulator.Actions; using Craftimizer.Solver; +using ObjectLayoutInspector; namespace Craftimizer.Benchmark; @@ -63,8 +64,11 @@ private static async Task RunTrace() private static async Task RunOther() { - //TypeLayout.PrintLayout>(true); - //return; + TypeLayout.PrintLayout(true); + TypeLayout.PrintLayout(true); + TypeLayout.PrintLayout(true); + TypeLayout.PrintLayout(true); + return; var input = new SimulationInput( new CharacterStats From c8b963d05113221d91516e93604b23498fbb45d9 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Sat, 11 Nov 2023 23:58:46 -0800 Subject: [PATCH 5/5] Memory optimization stuff --- Simulator/SimulationState.cs | 11 +---------- Solver/NodeScoresBuffer.cs | 13 +++++++------ 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/Simulator/SimulationState.cs b/Simulator/SimulationState.cs index 74cac04..8147cdb 100644 --- a/Simulator/SimulationState.cs +++ b/Simulator/SimulationState.cs @@ -18,7 +18,7 @@ public record struct SimulationState public ActionStates ActionStates; // https://github.com/ffxiv-teamcraft/simulator/blob/0682dfa76043ff4ccb38832c184d046ceaff0733/src/model/tables.ts#L2 - private static readonly int[] HQPercentTable = { + private static ReadOnlySpan HQPercentTable => new[] { 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 20, 20, 21, 22, 23, 24, 26, 28, 31, 34, 38, 42, 47, 52, 58, 64, 68, 71, @@ -44,13 +44,4 @@ public SimulationState(SimulationInput input) ActionCount = 0; ActionStates = new(); } - -#if IS_DETERMINISTIC - public override readonly string ToString() - { - var b = new System.Text.StringBuilder(); - PrintMembers(b); - return Convert.ToHexString(System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(b.ToString()))); - } -#endif } diff --git a/Solver/NodeScoresBuffer.cs b/Solver/NodeScoresBuffer.cs index fecfb40..5d45e59 100644 --- a/Solver/NodeScoresBuffer.cs +++ b/Solver/NodeScoresBuffer.cs @@ -7,11 +7,11 @@ namespace Craftimizer.Solver; // Adapted from https://github.com/dtao/ConcurrentList/blob/4fcf1c76e93021a41af5abb2d61a63caeba2adad/ConcurrentList/ConcurrentList.cs public struct NodeScoresBuffer { - public sealed class ScoresBatch + public readonly struct ScoresBatch { - public Memory ScoreSum; - public Memory MaxScore; - public Memory Visits; + public readonly Memory ScoreSum; + public readonly Memory MaxScore; + public readonly Memory Visits; public ScoresBatch() { @@ -40,9 +40,10 @@ public void Add() var idx = Count++; - var (arrayIdx, _) = GetArrayIndex(idx); + var (arrayIdx, subIdx) = GetArrayIndex(idx); - Data[arrayIdx] ??= new(); + if (subIdx == 0) + Data[arrayIdx] = new(); } public readonly void Visit((int arrayIdx, int subIdx) at, float score)