From 373507991af87370e289791adbe386a15e230409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20C=C3=A1ceres?= Date: Tue, 10 Sep 2024 14:18:51 +0200 Subject: [PATCH 1/2] Rent Game._moveStack (Move[1024]) from ArrayPool and make sure to return them when a Game is no longer used --- src/Lynx/Engine.cs | 4 +++- src/Lynx/FENParser.cs | 5 ++-- src/Lynx/Model/Game.cs | 49 ++++++++++++++++++++++++++++++++++---- src/Lynx/Model/Position.cs | 36 +++++++++++++++++++++++++--- src/Lynx/Search/IDDFS.cs | 2 +- 5 files changed, 85 insertions(+), 11 deletions(-) diff --git a/src/Lynx/Engine.cs b/src/Lynx/Engine.cs index b8d308962..0def3f7de 100644 --- a/src/Lynx/Engine.cs +++ b/src/Lynx/Engine.cs @@ -128,12 +128,14 @@ private void ResetEngine() internal void SetGame(Game game) { + Game.FreeResources(); Game = game; } public void NewGame() { AverageDepth = 0; + Game.FreeResources(); Game = new Game(Constants.InitialPositionFEN); _isNewGameComing = true; _isNewGameCommandSupported = true; @@ -151,7 +153,7 @@ public void NewGame() public void AdjustPosition(ReadOnlySpan rawPositionCommand) { Span moves = stackalloc Move[Constants.MaxNumberOfPossibleMovesInAPosition]; - + Game.FreeResources(); Game = PositionCommand.ParseGame(rawPositionCommand, moves); _isNewGameComing = false; _stopRequested = false; diff --git a/src/Lynx/FENParser.cs b/src/Lynx/FENParser.cs index 0a00779c7..56f79edb4 100644 --- a/src/Lynx/FENParser.cs +++ b/src/Lynx/FENParser.cs @@ -1,5 +1,6 @@ using Lynx.Model; using NLog; +using System.Buffers; using System.Runtime.CompilerServices; using ParseResult = (ulong[] PieceBitBoards, ulong[] OccupancyBitBoards, Lynx.Model.Side Side, byte Castle, Lynx.Model.BoardSquare EnPassant, @@ -16,8 +17,8 @@ public static ParseResult ParseFEN(ReadOnlySpan fen) { fen = fen.Trim(); - var pieceBitBoards = new BitBoard[12]; - var occupancyBitBoards = new BitBoard[3]; + var pieceBitBoards = ArrayPool.Shared.Rent(12); + var occupancyBitBoards = ArrayPool.Shared.Rent(3); bool success; Side side = Side.Both; diff --git a/src/Lynx/Model/Game.cs b/src/Lynx/Model/Game.cs index 3434a83d7..f0a9fdb9a 100644 --- a/src/Lynx/Model/Game.cs +++ b/src/Lynx/Model/Game.cs @@ -1,9 +1,11 @@ using NLog; +using System.Buffers; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace Lynx.Model; -public sealed class Game +public sealed class Game : IDisposable { private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); @@ -17,6 +19,7 @@ public sealed class Game /// Indexed by ply /// private readonly Move[] _moveStack; + private bool _disposedValue; public int HalfMovesWithoutCaptureOrPawnMove { get; set; } @@ -40,7 +43,9 @@ public Game(ReadOnlySpan fen, ReadOnlySpan rawMoves, Span ran PositionHashHistory = new(Constants.MaxNumberMovesInAGame) { CurrentPosition.UniqueIdentifier }; HalfMovesWithoutCaptureOrPawnMove = parsedFen.HalfMoveClock; - _moveStack = new Move[1024]; + + Debug.Assert(Constants.MaxNumberMovesInAGame < 1024, "Need to customized ArrayPool due to desired array size requirements"); + _moveStack = ArrayPool.Shared.Rent(Constants.MaxNumberMovesInAGame); #if DEBUG MoveHistory = new(Constants.MaxNumberMovesInAGame); @@ -210,13 +215,49 @@ public GameState MakeMove(Move moveToPlay) /// (either by the engine time management logic or by external stop command) /// currentPosition won't be the initial one /// - public void ResetCurrentPositionToBeforeSearchState() => CurrentPosition = new(PositionBeforeLastSearch); + public void ResetCurrentPositionToBeforeSearchState() + { + CurrentPosition.FreeResources(); + CurrentPosition = new(PositionBeforeLastSearch); + } - public void UpdateInitialPosition() => PositionBeforeLastSearch = new(CurrentPosition); + public void UpdateInitialPosition() + { + PositionBeforeLastSearch.FreeResources(); + PositionBeforeLastSearch = new(CurrentPosition); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PushToMoveStack(int n, Move move) => _moveStack[n + EvaluationConstants.ContinuationHistoryPlyCount] = move; [MethodImpl(MethodImplOptions.AggressiveInlining)] public Move PopFromMoveStack(int n) => _moveStack[n + EvaluationConstants.ContinuationHistoryPlyCount]; + + public void FreeResources() + { + ArrayPool.Shared.Return(_moveStack); + + CurrentPosition.FreeResources(); + PositionBeforeLastSearch.FreeResources(); + } + + private void Dispose(bool disposing) + { + _logger.Warn("Disposing Game instance"); + if (!_disposedValue) + { + if (disposing) + { + FreeResources(); + } + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } diff --git a/src/Lynx/Model/Position.cs b/src/Lynx/Model/Position.cs index 993fcf3fc..03d26a852 100644 --- a/src/Lynx/Model/Position.cs +++ b/src/Lynx/Model/Position.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; @@ -8,8 +9,10 @@ namespace Lynx.Model; -public class Position +public class Position : IDisposable { + private bool _disposedValue; + public long UniqueIdentifier { get; private set; } /// @@ -66,10 +69,10 @@ public Position((BitBoard[] PieceBitBoards, BitBoard[] OccupancyBitBoards, Side public Position(Position position) { UniqueIdentifier = position.UniqueIdentifier; - PieceBitBoards = new BitBoard[12]; + PieceBitBoards = ArrayPool.Shared.Rent(12); Array.Copy(position.PieceBitBoards, PieceBitBoards, position.PieceBitBoards.Length); - OccupancyBitBoards = new BitBoard[3]; + OccupancyBitBoards = ArrayPool.Shared.Rent(3); Array.Copy(position.OccupancyBitBoards, OccupancyBitBoards, position.OccupancyBitBoards.Length); Side = position.Side; @@ -1245,4 +1248,31 @@ public void PrintAttackedSquares(Side sideToMove) Console.Write("\n a b c d e f g h\n"); Console.WriteLine(separator); } + + public void FreeResources() + { + ArrayPool.Shared.Return(PieceBitBoards, clearArray: true); + ArrayPool.Shared.Return(OccupancyBitBoards, clearArray: true); + } + + protected virtual void Dispose(bool disposing) + { + Console.WriteLine("Disposing Position instance"); + + if (!_disposedValue) + { + if (disposing) + { + FreeResources(); + } + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } diff --git a/src/Lynx/Search/IDDFS.cs b/src/Lynx/Search/IDDFS.cs index 98dba45d1..c588ff723 100644 --- a/src/Lynx/Search/IDDFS.cs +++ b/src/Lynx/Search/IDDFS.cs @@ -309,7 +309,7 @@ private bool OnlyOneLegalMove(ref Move firstLegalMove, [NotNullWhen(true)] out S private SearchResult UpdateLastSearchResult(SearchResult? lastSearchResult, int bestEvaluation, int alpha, int beta, int depth, int mate) { - var pvMoves = _pVTable.TakeWhile(m => m != default).ToList(); + var pvMoves = _pVTable.TakeWhile(m => m != default).ToList(); // TODO optimized, use span slice? var maxDepthReached = _maxDepthReached.LastOrDefault(item => item != default); var elapsedTime = _stopWatch.ElapsedMilliseconds; From e6470381d112e9470cf74e13bc1a7b4a7e148519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20C=C3=A1ceres?= Date: Tue, 10 Sep 2024 15:17:50 +0200 Subject: [PATCH 2/2] Fix rented array size assert, <= 1024 --- src/Lynx/Model/Game.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Lynx/Model/Game.cs b/src/Lynx/Model/Game.cs index f0a9fdb9a..d6cd158c1 100644 --- a/src/Lynx/Model/Game.cs +++ b/src/Lynx/Model/Game.cs @@ -44,7 +44,7 @@ public Game(ReadOnlySpan fen, ReadOnlySpan rawMoves, Span ran PositionHashHistory = new(Constants.MaxNumberMovesInAGame) { CurrentPosition.UniqueIdentifier }; HalfMovesWithoutCaptureOrPawnMove = parsedFen.HalfMoveClock; - Debug.Assert(Constants.MaxNumberMovesInAGame < 1024, "Need to customized ArrayPool due to desired array size requirements"); + Debug.Assert(Constants.MaxNumberMovesInAGame <= 1024, "Need to customized ArrayPool due to desired array size requirements"); _moveStack = ArrayPool.Shared.Rent(Constants.MaxNumberMovesInAGame); #if DEBUG