Merge branch 'micro-optimizations' into main

This commit is contained in:
Asriel Camora
2023-11-12 00:15:00 -08:00
19 changed files with 66 additions and 50 deletions
+3 -1
View File
@@ -90,7 +90,9 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v3 uses: actions/setup-dotnet@v3
with: with:
dotnet-version: '7.0' dotnet-version: |
7.0
8.0
- name: Download Dalamud - name: Download Dalamud
run: | run: |
+19 -7
View File
@@ -1,15 +1,27 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnostics.dotTrace; using BenchmarkDotNet.Diagnostics.dotTrace;
using BenchmarkDotNet.Jobs;
using Craftimizer.Simulator; using Craftimizer.Simulator;
using Craftimizer.Solver; using Craftimizer.Solver;
using System.Security.Cryptography;
using System.Text;
namespace Craftimizer.Benchmark; namespace Craftimizer.Benchmark;
[SimpleJob(iterationCount: 10)] [SimpleJob(RuntimeMoniker.Net70, baseline: true)]
[SimpleJob(RuntimeMoniker.Net80)]
[MinColumn, Q1Column, Q3Column, MaxColumn] [MinColumn, Q1Column, Q3Column, MaxColumn]
[DotTraceDiagnoser] [DotTraceDiagnoser]
public class Bench public class Bench
{ {
public record struct SHAWrapper<T>(T Data) where T : notnull
{
public static implicit operator T(SHAWrapper<T> wrapper) => wrapper.Data;
public override readonly string ToString() =>
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(Data.ToString()!)));
}
private static SimulationInput[] Inputs { get; } = new SimulationInput[] { private static SimulationInput[] Inputs { get; } = new SimulationInput[] {
// https://craftingway.app/rotation/loud-namazu-jVe9Y // https://craftingway.app/rotation/loud-namazu-jVe9Y
// Chondrite Saw // Chondrite Saw
@@ -68,22 +80,22 @@ public class Bench
}) })
}; };
public static IEnumerable<SimulationState> States => Inputs.Select(i => new SimulationState(i)); public static IEnumerable<SHAWrapper<SimulationState>> States => Inputs.Select(i => new SHAWrapper<SimulationState>(new(i)));
public static IEnumerable<SolverConfig> Configs => new SolverConfig[] public static IEnumerable<SHAWrapper<SolverConfig>> Configs => new SHAWrapper<SolverConfig>[]
{ {
new() new(new()
{ {
Algorithm = SolverAlgorithm.Stepwise, Algorithm = SolverAlgorithm.Stepwise,
Iterations = 30_000, Iterations = 30_000,
} })
}; };
[ParamsSource(nameof(States))] [ParamsSource(nameof(States))]
public SimulationState State { get; set; } public SHAWrapper<SimulationState> State { get; set; }
[ParamsSource(nameof(Configs))] [ParamsSource(nameof(Configs))]
public SolverConfig Config { get; set; } public SHAWrapper<SolverConfig> Config { get; set; }
[Benchmark] [Benchmark]
public async Task<float> Solve() public async Task<float> Solve()
+6 -2
View File
@@ -1,6 +1,7 @@
using Craftimizer.Simulator; using Craftimizer.Simulator;
using Craftimizer.Simulator.Actions; using Craftimizer.Simulator.Actions;
using Craftimizer.Solver; using Craftimizer.Solver;
using ObjectLayoutInspector;
namespace Craftimizer.Benchmark; namespace Craftimizer.Benchmark;
@@ -63,8 +64,11 @@ internal static class Program
private static async Task RunOther() private static async Task RunOther()
{ {
//TypeLayout.PrintLayout<ArenaNode<SimulationNode>>(true); TypeLayout.PrintLayout<SimulationState>(true);
//return; TypeLayout.PrintLayout<Simulator.Simulator>(true);
TypeLayout.PrintLayout<BaseAction>(true);
TypeLayout.PrintLayout<SimulationNode>(true);
return;
var input = new SimulationInput( var input = new SimulationInput(
new CharacterStats new CharacterStats
+2 -8
View File
@@ -10,7 +10,7 @@ public abstract class BaseComboAction : BaseAction
protected bool BaseCanUse(Simulator s) => protected bool BaseCanUse(Simulator s) =>
base.CanUse(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); var wasteNots = effects.HasEffect(EffectType.WasteNot) || effects.HasEffect(EffectType.WasteNot2);
// -A // -A
@@ -23,13 +23,10 @@ public abstract class BaseComboAction : BaseAction
return true; return true;
} }
public static bool VerifyDurability2(SimulationState s, int durabilityA) =>
VerifyDurability2(durabilityA, s.Durability, s.ActiveEffects);
public static bool VerifyDurability2(Simulator s, int durabilityA) => public static bool VerifyDurability2(Simulator s, int durabilityA) =>
VerifyDurability2(durabilityA, s.Durability, s.ActiveEffects); 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 wasteNots = Math.Max(effects.GetDuration(EffectType.WasteNot), effects.GetDuration(EffectType.WasteNot2));
var manips = effects.HasEffect(EffectType.Manipulation); var manips = effects.HasEffect(EffectType.Manipulation);
@@ -56,7 +53,4 @@ public abstract class BaseComboAction : BaseAction
public static bool VerifyDurability3(Simulator s, int durabilityA, int durabilityB) => public static bool VerifyDurability3(Simulator s, int durabilityA, int durabilityB) =>
VerifyDurability3(durabilityA, durabilityB, s.Durability, s.ActiveEffects); VerifyDurability3(durabilityA, durabilityB, s.Durability, s.ActiveEffects);
public static bool VerifyDurability3(SimulationState s, int durabilityA, int durabilityB) =>
VerifyDurability3(durabilityA, durabilityB, s.Durability, s.ActiveEffects);
} }
+1 -1
View File
@@ -18,7 +18,7 @@ public record struct SimulationState
public ActionStates ActionStates; public ActionStates ActionStates;
// https://github.com/ffxiv-teamcraft/simulator/blob/0682dfa76043ff4ccb38832c184d046ceaff0733/src/model/tables.ts#L2 // https://github.com/ffxiv-teamcraft/simulator/blob/0682dfa76043ff4ccb38832c184d046ceaff0733/src/model/tables.ts#L2
private static readonly int[] HQPercentTable = { private static ReadOnlySpan<int> 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, 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, 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, 17, 18, 18, 18, 19, 19, 20, 20, 21, 22, 23, 24, 26, 28, 31, 34, 38, 42, 47, 52, 58, 64, 68, 71,
+4 -4
View File
@@ -35,17 +35,17 @@ public class Simulator
public IEnumerable<ActionType> AvailableActions => ActionUtils.AvailableActions(this); public IEnumerable<ActionType> AvailableActions => ActionUtils.AvailableActions(this);
public Simulator(SimulationState state) public Simulator(in SimulationState state)
{ {
State = state; State = state;
} }
public void SetState(SimulationState state) public void SetState(in SimulationState state)
{ {
State = 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; State = state;
return (Execute(action), State); return (Execute(action), State);
@@ -75,7 +75,7 @@ public class Simulator
return ActionResponse.UsedAction; return ActionResponse.UsedAction;
} }
public (ActionResponse Response, SimulationState NewState, int FailedActionIdx) ExecuteMultiple(SimulationState state, IEnumerable<ActionType> actions) public (ActionResponse Response, SimulationState NewState, int FailedActionIdx) ExecuteMultiple(in SimulationState state, IEnumerable<ActionType> actions)
{ {
State = state; State = state;
var i = 0; var i = 0;
+1 -1
View File
@@ -2,7 +2,7 @@ namespace Craftimizer.Simulator;
public class SimulatorNoRandom : Simulator public class SimulatorNoRandom : Simulator
{ {
public SimulatorNoRandom(SimulationState state) : base(state) public SimulatorNoRandom(in SimulationState state) : base(state)
{ {
} }
+1 -1
View File
@@ -9,7 +9,7 @@ public struct ActionSet
{ {
private uint bits; private uint bits;
public static readonly ActionType[] AcceptedActions = new[] internal static ReadOnlySpan<ActionType> AcceptedActions => new[]
{ {
ActionType.StandardTouchCombo, ActionType.StandardTouchCombo,
ActionType.AdvancedTouchCombo, ActionType.AdvancedTouchCombo,
+6
View File
@@ -20,6 +20,12 @@
<ProjectReference Include="..\Simulator\Craftimizer.Simulator.csproj" /> <ProjectReference Include="..\Simulator\Craftimizer.Simulator.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Craftimizer.Test</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<PropertyGroup Condition="'$(IS_BENCH)'=='1'"> <PropertyGroup Condition="'$(IS_BENCH)'=='1'">
<DefineConstants>$(DefineConstants);IS_DETERMINISTIC</DefineConstants> <DefineConstants>$(DefineConstants);IS_DETERMINISTIC</DefineConstants>
</PropertyGroup> </PropertyGroup>
+6 -6
View File
@@ -17,7 +17,7 @@ public sealed class MCTS
public float MaxScore => rootScores.MaxScore; public float MaxScore => rootScores.MaxScore;
public MCTS(MCTSConfig config, SimulationState state) public MCTS(in MCTSConfig config, in SimulationState state)
{ {
this.config = config; this.config = config;
var sim = new Simulator(state, config.MaxStepCount); var sim = new Simulator(state, config.MaxStepCount);
@@ -30,7 +30,7 @@ public sealed class MCTS
rootScores = new(); 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); (_, var newState) = simulator.Execute(state, action);
return new( return new(
@@ -61,7 +61,7 @@ public sealed class MCTS
[Pure] [Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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 length = scores.Count;
var vecLength = Vector<float>.Count; var vecLength = Vector<float>.Count;
@@ -111,7 +111,7 @@ public sealed class MCTS
float explorationConstant, float explorationConstant,
float maxScoreWeightingConstant, float maxScoreWeightingConstant,
int parentVisits, int parentVisits,
ref NodeScoresBuffer scores) in NodeScoresBuffer scores)
{ {
var length = scores.Count; var length = scores.Count;
var vecLength = Vector<float>.Count; var vecLength = Vector<float>.Count;
@@ -168,7 +168,7 @@ public sealed class MCTS
return node; return node;
// select the node with the highest score // 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); nodeVisits = node.ChildScores.GetVisits(at);
node = node.ChildAt(at)!; node = node.ChildAt(at)!;
} }
@@ -320,7 +320,7 @@ public sealed class MCTS
while (node.Children.Count != 0) while (node.Children.Count != 0)
{ {
node = node.ChildAt(ChildMaxScore(ref node.ChildScores))!; node = node.ChildAt(ChildMaxScore(in node.ChildScores))!;
if (node.State.Action != null) if (node.State.Action != null)
actions.Add(node.State.Action.Value); actions.Add(node.State.Action.Value);
+2 -2
View File
@@ -1,4 +1,4 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Craftimizer.Solver; namespace Craftimizer.Solver;
@@ -21,7 +21,7 @@ public readonly record struct MCTSConfig
public float ScoreCP { get; init; } public float ScoreCP { get; init; }
public float ScoreSteps { get; init; } public float ScoreSteps { get; init; }
public MCTSConfig(SolverConfig config) public MCTSConfig(in SolverConfig config)
{ {
MaxStepCount = config.MaxStepCount; MaxStepCount = config.MaxStepCount;
MaxRolloutStepCount = config.MaxRolloutStepCount; MaxRolloutStepCount = config.MaxRolloutStepCount;
+7 -6
View File
@@ -7,11 +7,11 @@ namespace Craftimizer.Solver;
// Adapted from https://github.com/dtao/ConcurrentList/blob/4fcf1c76e93021a41af5abb2d61a63caeba2adad/ConcurrentList/ConcurrentList.cs // Adapted from https://github.com/dtao/ConcurrentList/blob/4fcf1c76e93021a41af5abb2d61a63caeba2adad/ConcurrentList/ConcurrentList.cs
public struct NodeScoresBuffer public struct NodeScoresBuffer
{ {
public sealed class ScoresBatch public readonly struct ScoresBatch
{ {
public Memory<float> ScoreSum; public readonly Memory<float> ScoreSum;
public Memory<float> MaxScore; public readonly Memory<float> MaxScore;
public Memory<int> Visits; public readonly Memory<int> Visits;
public ScoresBatch() public ScoresBatch()
{ {
@@ -40,9 +40,10 @@ public struct NodeScoresBuffer
var idx = Count++; 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) public readonly void Visit((int arrayIdx, int subIdx) at, float score)
+3 -3
View File
@@ -19,7 +19,7 @@ public struct SimulationNode
public readonly bool IsComplete => CompletionState != CompletionState.Incomplete; 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; State = state;
Action = action; Action = action;
@@ -32,10 +32,10 @@ public struct SimulationNode
CompletionState.NoMoreActions : CompletionState.NoMoreActions :
simCompletionState; simCompletionState;
public readonly float? CalculateScore(MCTSConfig config) => public readonly float? CalculateScore(in MCTSConfig config) =>
CalculateScoreForState(State, SimulationCompletionState, 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) if (completionState != CompletionState.ProgressComplete)
return null; return null;
+1 -1
View File
@@ -20,7 +20,7 @@ internal sealed class Simulator : SimulatorNoRandom
} }
} }
public Simulator(SimulationState state, int maxStepCount) : base(state) public Simulator(in SimulationState state, int maxStepCount) : base(state)
{ {
this.maxStepCount = maxStepCount; this.maxStepCount = maxStepCount;
} }
+1 -1
View File
@@ -35,7 +35,7 @@ public sealed class Solver : IDisposable
// Always called when a new step is generated. // Always called when a new step is generated.
public event NewActionDelegate? OnNewAction; public event NewActionDelegate? OnNewAction;
public Solver(SolverConfig config, SimulationState state) public Solver(in SolverConfig config, in SimulationState state)
{ {
Config = config; Config = config;
State = state; State = state;
+1 -3
View File
@@ -1,5 +1,3 @@
using Craftimizer.Simulator.Actions;
using Craftimizer.Simulator;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Craftimizer.Solver; namespace Craftimizer.Solver;
@@ -71,4 +69,4 @@ public readonly record struct SolverConfig
FurcatedActionCount = Environment.ProcessorCount / 2, FurcatedActionCount = Environment.ProcessorCount / 2,
Algorithm = SolverAlgorithm.StepwiseForked Algorithm = SolverAlgorithm.StepwiseForked
}; };
} }
+1 -1
View File
@@ -9,7 +9,7 @@ public readonly record struct SolverSolution {
public readonly IEnumerable<ActionType> ActionEnumerable { init => actions = SanitizeCombos(value).ToList(); } public readonly IEnumerable<ActionType> ActionEnumerable { init => actions = SanitizeCombos(value).ToList(); }
public readonly SimulationState State { get; init; } public readonly SimulationState State { get; init; }
public SolverSolution(IEnumerable<ActionType> actions, SimulationState state) public SolverSolution(IEnumerable<ActionType> actions, in SimulationState state)
{ {
ActionEnumerable = actions; ActionEnumerable = actions;
State = state; State = state;
-2
View File
@@ -1,5 +1,3 @@
using Craftimizer.Simulator.Actions;
namespace Craftimizer.Test.Solver; namespace Craftimizer.Test.Solver;
[TestClass] [TestClass]
+1
View File
@@ -1,3 +1,4 @@
global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Microsoft.VisualStudio.TestTools.UnitTesting;
global using Craftimizer.Solver; global using Craftimizer.Solver;
global using Craftimizer.Simulator; global using Craftimizer.Simulator;
global using Craftimizer.Simulator.Actions;