From adf3a80ba541392728f643683c26226ab5321045 Mon Sep 17 00:00:00 2001 From: Asriel Camora Date: Sat, 11 Nov 2023 14:56:59 -0800 Subject: [PATCH] Memory-related improvements --- Benchmark/Bench.cs | 14 +++++++++--- Benchmark/Program.cs | 8 +++++-- Craftimizer/Utils/Gearsets.cs | 11 ++++----- Simulator/Actions/ActionType.cs | 4 ++-- Simulator/Effects.cs | 2 +- Simulator/SimulationState.cs | 13 ++--------- Solver/MCTS.cs | 12 +++++----- Solver/NodeScoresBuffer.cs | 40 ++++++++++++++++++--------------- Solver/SimulationNode.cs | 2 +- 9 files changed, 57 insertions(+), 49 deletions(-) diff --git a/Benchmark/Bench.cs b/Benchmark/Bench.cs index d094474..d044358 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,12 @@ namespace Craftimizer.Benchmark; [DotTraceDiagnoser] public class Bench { + public record struct SimStateWrapper(SimulationState State) + { + public override readonly string ToString() => + Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(State.ToString()))); + } + private static SimulationInput[] Inputs { get; } = new SimulationInput[] { // https://craftingway.app/rotation/loud-namazu-jVe9Y // Chondrite Saw @@ -70,7 +78,7 @@ public class Bench }) }; - public static IEnumerable States => Inputs.Select(i => new SimulationState(i)); + public static IEnumerable States => Inputs.Select(i => new SimStateWrapper(new SimulationState(i))); public static IEnumerable Configs => new SolverConfig[] { @@ -82,7 +90,7 @@ public class Bench }; [ParamsSource(nameof(States))] - public SimulationState State { get; set; } + public SimStateWrapper State { get; set; } [ParamsSource(nameof(Configs))] public SolverConfig Config { get; set; } @@ -90,7 +98,7 @@ public class Bench [Benchmark] public async Task Solve() { - var solver = new Solver.Solver(Config, State); + var solver = new Solver.Solver(Config, State.State); solver.Start(); var (_, s) = await solver.GetTask().ConfigureAwait(false); return (float)s.Quality / s.Input.Recipe.MaxQuality; 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 diff --git a/Craftimizer/Utils/Gearsets.cs b/Craftimizer/Utils/Gearsets.cs index b3bc64e..5bad942 100644 --- a/Craftimizer/Utils/Gearsets.cs +++ b/Craftimizer/Utils/Gearsets.cs @@ -20,20 +20,21 @@ public record struct GearsetItem(uint itemId, bool isHq, GearsetMateria[] materi public const int ParamCraftsmanship = 70; public const int ParamControl = 71; - private static readonly int[] LevelToCLvlLUT; + private static ReadOnlyMemory LevelToCLvlLUT { get; } static Gearsets() { - LevelToCLvlLUT = new int[90]; + var lut = new int[90]; for (uint i = 0; i < 80; ++i) { var level = i + 1; - LevelToCLvlLUT[i] = LuminaSheets.ParamGrowSheet.GetRow(level)!.CraftingLevel; + lut[i] = LuminaSheets.ParamGrowSheet.GetRow(level)!.CraftingLevel; } for (var i = 80; i < 90; ++i) { var level = i + 1; - LevelToCLvlLUT[i] = (int)LuminaSheets.RecipeLevelTableSheet.First(r => r.ClassJobLevel == level).RowId; + lut[i] = (int)LuminaSheets.RecipeLevelTableSheet.First(r => r.ClassJobLevel == level).RowId; } + LevelToCLvlLUT = lut; } public static void Initialize() { } @@ -148,7 +149,7 @@ public static bool IsSplendorousTool(GearsetItem item) => public static int CalculateCLvl(int level) => (level > 0 && level <= 90) ? - LevelToCLvlLUT[level - 1] : + LevelToCLvlLUT.Span[level - 1] : throw new ArgumentOutOfRangeException(nameof(level), level, "Level is out of range."); // https://github.com/ffxiv-teamcraft/ffxiv-teamcraft/blob/24d0db2d9676f264edf53651b21005305267c84c/apps/client/src/app/modules/gearsets/materia.service.ts#L265 diff --git a/Simulator/Actions/ActionType.cs b/Simulator/Actions/ActionType.cs index 1c6e589..d3c3407 100644 --- a/Simulator/Actions/ActionType.cs +++ b/Simulator/Actions/ActionType.cs @@ -47,7 +47,7 @@ public enum ActionType : byte public static class ActionUtils { - private static readonly BaseAction[] Actions; + private static ReadOnlyMemory Actions { get; } static ActionUtils() { @@ -61,7 +61,7 @@ static ActionUtils() [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BaseAction Base(this ActionType me) => Actions[(int)me]; + public static BaseAction Base(this ActionType me) => Actions.Span[(int)me]; public static IEnumerable AvailableActions(Simulator simulation) => simulation.IsComplete diff --git a/Simulator/Effects.cs b/Simulator/Effects.cs index 6740e16..e047723 100644 --- a/Simulator/Effects.cs +++ b/Simulator/Effects.cs @@ -4,7 +4,7 @@ namespace Craftimizer.Simulator; -[StructLayout(LayoutKind.Auto)] +[StructLayout(LayoutKind.Sequential, Pack = 1)] public record struct Effects { public byte InnerQuiet; diff --git a/Simulator/SimulationState.cs b/Simulator/SimulationState.cs index 74cac04..f2f40ea 100644 --- a/Simulator/SimulationState.cs +++ b/Simulator/SimulationState.cs @@ -2,7 +2,7 @@ namespace Craftimizer.Simulator; -[StructLayout(LayoutKind.Auto)] +[StructLayout(LayoutKind.Sequential, Pack = 1)] public record struct SimulationState { public readonly SimulationInput Input; @@ -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/MCTS.cs b/Solver/MCTS.cs index 76251db..c340018 100644 --- a/Solver/MCTS.cs +++ b/Solver/MCTS.cs @@ -72,8 +72,8 @@ private static (int arrayIdx, int subIdx) ChildMaxScore(in NodeScoresBuffer scor { var iterCount = Math.Min(vecLength, length); - ref var chunk = ref scores.Data[i]; - var m = new Vector(chunk.MaxScore.Span); + ref var chunk = ref scores.Data!.Value.Span[i]; + var m = new Vector(chunk.MaxScore); var idx = Intrinsics.HMaxIndex(m, iterCount); @@ -127,10 +127,10 @@ private static (int arrayIdx, int subIdx) EvalBestChild( { var iterCount = Math.Min(vecLength, length); - ref var chunk = ref scores.Data[i]; - var s = new Vector(chunk.ScoreSum.Span); - var vInt = new Vector(chunk.Visits.Span); - var m = new Vector(chunk.MaxScore.Span); + ref var chunk = ref scores.Data!.Value.Span[i]; + var s = new Vector(chunk.ScoreSum); + var vInt = new Vector(chunk.Visits); + var m = new Vector(chunk.MaxScore); vInt = Vector.Max(vInt, Vector.One); var v = Vector.ConvertToSingle(vInt); diff --git a/Solver/NodeScoresBuffer.cs b/Solver/NodeScoresBuffer.cs index fecfb40..36c3077 100644 --- a/Solver/NodeScoresBuffer.cs +++ b/Solver/NodeScoresBuffer.cs @@ -1,24 +1,26 @@ using System.Diagnostics.Contracts; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; 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 ScoresBatch() - { - ScoreSum = new float[BatchSize]; - MaxScore = new float[BatchSize]; - Visits = new int[BatchSize]; - } + private readonly Memory data; + + public Span ScoreSum => + MemoryMarshal.Cast(data.Span.Slice(0, BatchSize * sizeof(float))); + public Span MaxScore => + MemoryMarshal.Cast(data.Span.Slice(BatchSize * sizeof(float), BatchSize * sizeof(float))); + public Span Visits => + MemoryMarshal.Cast(data.Span.Slice(BatchSize * sizeof(float) * 2, BatchSize * sizeof(int))); + + public ScoresBatch() => + data = new byte[BatchSize * sizeof(float) * 3]; } // Technically 25, but it's very unlikely to actually get to there. @@ -31,7 +33,7 @@ public ScoresBatch() private static readonly int BatchCount = MaxSize / BatchSize; - public ScoresBatch[] Data; + public Memory? Data; public int Count { get; private set; } public void Add() @@ -40,20 +42,22 @@ public void Add() var idx = Count++; - var (arrayIdx, _) = GetArrayIndex(idx); + var (arrayIdx, subIdx) = GetArrayIndex(idx); - Data[arrayIdx] ??= new(); + if (subIdx == 0) + Data.Value.Span[arrayIdx] = new(); } public readonly void Visit((int arrayIdx, int subIdx) at, float score) { - Data[at.arrayIdx].ScoreSum.Span[at.subIdx] += score; - Data[at.arrayIdx].MaxScore.Span[at.subIdx] = Math.Max(Data[at.arrayIdx].MaxScore.Span[at.subIdx], score); - Data[at.arrayIdx].Visits.Span[at.subIdx]++; + ref var batch = ref Data!.Value.Span[at.arrayIdx]; + batch.ScoreSum[at.subIdx] += score; + batch.MaxScore[at.subIdx] = Math.Max(batch.MaxScore[at.subIdx], score); + batch.Visits[at.subIdx]++; } public readonly int GetVisits((int arrayIdx, int subIdx) at) => - Data[at.arrayIdx].Visits.Span[at.subIdx]; + Data!.Value.Span[at.arrayIdx].Visits[at.subIdx]; [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Solver/SimulationNode.cs b/Solver/SimulationNode.cs index a2b8f34..01fe352 100644 --- a/Solver/SimulationNode.cs +++ b/Solver/SimulationNode.cs @@ -6,7 +6,7 @@ namespace Craftimizer.Solver; -[StructLayout(LayoutKind.Auto)] +[StructLayout(LayoutKind.Sequential, Pack = 1)] public struct SimulationNode { public readonly SimulationState State;