From db99487f4eeb6b51a5e234dfaac065f445042935 Mon Sep 17 00:00:00 2001 From: Liam McGuire <12057628+liamt19@users.noreply.github.com> Date: Sat, 22 Jun 2024 16:36:34 -0400 Subject: [PATCH] General cleanups (#46) bench: 7141099 --- Logic/Core/StateInfo.cs | 61 ++---- Logic/Data/Enums.cs | 13 ++ Logic/Data/Move.cs | 7 +- Logic/NN/Accumulator.cs | 6 - Logic/NN/Bucketed768.cs | 20 +- Logic/NN/Bucketed768Unroll.cs | 3 +- Logic/NN/FunUnrollThings.cs | 4 +- Logic/NN/VSize.cs | 29 --- Logic/Search/SearchInformation.cs | 5 +- Logic/Search/SearchStackEntry.cs | 122 ++---------- Logic/Transposition/TTCluster.cs | 19 +- Logic/Transposition/TranspositionTable.cs | 11 +- Logic/UCI/UCIClient.cs | 34 +--- Logic/Util/FishBench.cs | 2 - Logic/Util/SearchBench.cs | 1 - Logic/Util/SkipStaticConstructorAttribute.cs | 11 -- Logic/Util/Utilities.cs | 196 +++++-------------- Program.cs | 4 +- 18 files changed, 136 insertions(+), 412 deletions(-) delete mode 100644 Logic/NN/VSize.cs delete mode 100644 Logic/Util/SkipStaticConstructorAttribute.cs diff --git a/Logic/Core/StateInfo.cs b/Logic/Core/StateInfo.cs index dfed24ba..e3684640 100644 --- a/Logic/Core/StateInfo.cs +++ b/Logic/Core/StateInfo.cs @@ -15,55 +15,20 @@ namespace Lizard.Logic.Core public unsafe struct StateInfo { public static readonly nuint StateCopySize = (nuint)(sizeof(StateInfo) - sizeof(Accumulator*)); - static StateInfo() - { - int accOffset = ((FieldOffsetAttribute)typeof(StateInfo).GetField("Accumulator").GetCustomAttributes(typeof(FieldOffsetAttribute), true)[0]).Value; - Assert(accOffset == (int)StateCopySize, - $"StateInfo's Accumulator pointer is {accOffset} / {sizeof(StateInfo)}, should be {StateCopySize}"); - } - - [FieldOffset(0)] - public fixed ulong CheckSquares[PieceNB]; - - [FieldOffset(48)] - public fixed int KingSquares[2]; - - [FieldOffset(56)] - public fixed ulong BlockingPieces[2]; - - [FieldOffset(72)] - public fixed ulong Pinners[2]; - - [FieldOffset(88)] - public ulong Hash = 0; - - [FieldOffset(96)] - public ulong Checkers = 0; - - [FieldOffset(104)] - public CastlingStatus CastleStatus = CastlingStatus.None; - - /// - /// The first number in the FEN, which starts at 0 and resets to 0 every time a pawn moves or a piece is captured. - /// If this reaches 100, the game is a draw by the 50-move rule. - /// - [FieldOffset(108)] - public int HalfmoveClock = 0; - - [FieldOffset(112)] - public int EPSquare = EPNone; - - [FieldOffset(116)] - public int CapturedPiece = None; - - [FieldOffset(120)] - public int PliesFromNull = 0; - - [FieldOffset(124)] - private int _align = 0; - [FieldOffset(128)] - public Accumulator* Accumulator; + [FieldOffset( 0)] public fixed ulong CheckSquares[PieceNB]; + [FieldOffset( 48)] public fixed ulong BlockingPieces[2]; + [FieldOffset( 64)] public fixed ulong Pinners[2]; + [FieldOffset( 80)] public fixed int KingSquares[2]; + [FieldOffset( 88)] public ulong Hash = 0; + [FieldOffset( 96)] public ulong Checkers = 0; + [FieldOffset(104)] public int HalfmoveClock = 0; + [FieldOffset(108)] public int EPSquare = EPNone; + [FieldOffset(112)] public int CapturedPiece = None; + [FieldOffset(116)] public int PliesFromNull = 0; + [FieldOffset(120)] public CastlingStatus CastleStatus = CastlingStatus.None; + [FieldOffset(124)] private fixed byte _pad0[4]; + [FieldOffset(128)] public Accumulator* Accumulator; public StateInfo() { diff --git a/Logic/Data/Enums.cs b/Logic/Data/Enums.cs index 4d8b865f..f77a3bba 100644 --- a/Logic/Data/Enums.cs +++ b/Logic/Data/Enums.cs @@ -99,6 +99,19 @@ public static class Files public const int H = 7; } + public static class Direction + { + public const int NORTH = 8; + public const int EAST = 1; + public const int SOUTH = -NORTH; + public const int WEST = -EAST; + + public const int NORTH_EAST = NORTH + EAST; + public const int SOUTH_EAST = SOUTH + EAST; + public const int SOUTH_WEST = SOUTH + WEST; + public const int NORTH_WEST = NORTH + WEST; + } + public static class Squares { public const int A1 = 0; diff --git a/Logic/Data/Move.cs b/Logic/Data/Move.cs index 942a6b43..7cbeb710 100644 --- a/Logic/Data/Move.cs +++ b/Logic/Data/Move.cs @@ -1,8 +1,13 @@ -using System.Text; + +#pragma warning disable CS0660 // Type defines operator == or operator != but does not override Object.Equals(object o) +#pragma warning disable CS0661 // Type defines operator == or operator != but does not override Object.GetHashCode() + +using System.Text; namespace Lizard.Logic.Data { + public unsafe struct Move { public static readonly Move Null = new Move(); diff --git a/Logic/NN/Accumulator.cs b/Logic/NN/Accumulator.cs index 58fb45cf..a3d13adf 100644 --- a/Logic/NN/Accumulator.cs +++ b/Logic/NN/Accumulator.cs @@ -8,14 +8,8 @@ namespace Lizard.Logic.NN /// public unsafe struct Accumulator { - // Intellisense incorrectly marks "var accumulation = accumulator[perspectives[p]]" in FeatureTransformer.TransformFeatures - // as an error when this uses a primary constructor with "size" as a parameter. - // https://github.com/dotnet/roslyn/issues/69663 - public const int ByteSize = Bucketed768.HiddenSize * sizeof(short); - public static int VectorCount => Bucketed768.HiddenSize / VSize.Short; - public readonly Vector256* White; public readonly Vector256* Black; diff --git a/Logic/NN/Bucketed768.cs b/Logic/NN/Bucketed768.cs index 03ad4050..acc2965d 100644 --- a/Logic/NN/Bucketed768.cs +++ b/Logic/NN/Bucketed768.cs @@ -8,7 +8,6 @@ namespace Lizard.Logic.NN { - [SkipStaticConstructor] public static unsafe partial class Bucketed768 { public const int InputBuckets = 5; @@ -18,19 +17,14 @@ public static unsafe partial class Bucketed768 public const int QA = 255; public const int QB = 64; - private const int QAB = QA * QB; public const int OutputScale = 400; - public static readonly int SIMD_CHUNKS_512 = HiddenSize / Vector512.Count; - public static readonly int SIMD_CHUNKS_256 = HiddenSize / Vector256.Count; - /// /// (768x5 -> 1536)x2 -> 8 /// public const string NetworkName = "L1536x5x8_g75_s20-550.bin"; - public static readonly short* FeatureWeights; public static readonly short* FeatureBiases; public static readonly short* LayerWeights; @@ -42,7 +36,7 @@ public static unsafe partial class Bucketed768 private const int LayerWeightElements = HiddenSize * 2 * OutputBuckets; private const int LayerBiasElements = OutputBuckets; - public static long ExpectedNetworkSize => (FeatureWeightElements + FeatureBiasElements + LayerWeightElements + LayerBiasElements) * sizeof(short); + private const long ExpectedNetworkSize = (FeatureWeightElements + FeatureBiasElements + LayerWeightElements + LayerBiasElements) * sizeof(short); private static ReadOnlySpan KingBuckets => [ @@ -64,7 +58,7 @@ static Bucketed768() FeatureBiases = (short*)AlignedAllocZeroed(sizeof(short) * FeatureBiasElements); LayerWeights = (short*)AlignedAllocZeroed(sizeof(short) * LayerWeightElements); - LayerBiases = (short*)AlignedAllocZeroed(sizeof(short) * (nuint)Math.Max(LayerBiasElements, VSize.Short)); + LayerBiases = (short*)AlignedAllocZeroed(sizeof(short) * (nuint)Math.Max(LayerBiasElements, Vector512.Count)); string networkToLoad = NetworkName; @@ -88,7 +82,7 @@ public static void Initialize(string networkToLoad, bool exitIfFail = true) if (stream.Position + toRead > stream.Length) { Console.WriteLine("Bucketed768's BinaryReader doesn't have enough data for all weights and biases to be read!"); - Console.WriteLine("It expects to read " + toRead + " bytes, but the stream's position is " + stream.Position + "/" + stream.Length); + Console.WriteLine($"It expects to read {toRead} bytes, but the stream's position is {stream.Position} / {stream.Length}"); Console.WriteLine("The file being loaded is either not a valid 768 network, or has different layer sizes than the hardcoded ones."); if (exitIfFail) { @@ -242,6 +236,8 @@ public static int GetEvaluation(Position pos) Vector256 zeroVec = Vector256.Zero; Vector256 sum = Vector256.Zero; + int SimdChunks = HiddenSize / Vector256.Count; + // Formula from BlackMarlin int occ = (int)popcount(pos.bb.Occupancy); int outputBucket = Math.Min((63 - occ) * (32 - occ) / 225, 7); @@ -251,7 +247,7 @@ public static int GetEvaluation(Position pos) var ourWeights = (Vector256*)(LayerWeights + (outputBucket * (HiddenSize * 2))); var theirWeights = (Vector256*)(LayerWeights + (outputBucket * (HiddenSize * 2)) + HiddenSize); - for (int i = 0; i < SIMD_CHUNKS_256; i++) + for (int i = 0; i < SimdChunks; i++) { Vector256 clamp = Vector256.Min(maxVec, Vector256.Max(zeroVec, ourData[i])); Vector256 mult = clamp * ourWeights[i]; @@ -262,7 +258,7 @@ public static int GetEvaluation(Position pos) sum = Vector256.Add(sum, Vector256.Add(loMult * loClamp, hiMult * hiClamp)); } - for (int i = 0; i < SIMD_CHUNKS_256; i++) + for (int i = 0; i < SimdChunks; i++) { Vector256 clamp = Vector256.Min(maxVec, Vector256.Max(zeroVec, theirData[i])); Vector256 mult = clamp * theirWeights[i]; @@ -275,7 +271,7 @@ public static int GetEvaluation(Position pos) int output = Vector256.Sum(sum); - return (output / QA + LayerBiases[outputBucket]) * OutputScale / QAB; + return (output / QA + LayerBiases[outputBucket]) * OutputScale / (QA * QB); } private static int FeatureIndexSingle(int pc, int pt, int sq, int kingSq, int perspective) diff --git a/Logic/NN/Bucketed768Unroll.cs b/Logic/NN/Bucketed768Unroll.cs index 82286435..f586a702 100644 --- a/Logic/NN/Bucketed768Unroll.cs +++ b/Logic/NN/Bucketed768Unroll.cs @@ -11,6 +11,7 @@ using VShort = System.Runtime.Intrinsics.Vector256; #endif +#pragma warning disable CS0162 // Unreachable code detected namespace Lizard.Logic.NN { @@ -501,7 +502,7 @@ public static int GetEvaluationUnrolled512(Position pos) int output = NNUE.SumVectorNoHadd(sumVec); - return (output / QA + LayerBiases[outputBucket]) * 400 / (QA * QB); + return (output / QA + LayerBiases[outputBucket]) * OutputScale / (QA * QB); } } diff --git a/Logic/NN/FunUnrollThings.cs b/Logic/NN/FunUnrollThings.cs index 455b4ba7..56c62d06 100644 --- a/Logic/NN/FunUnrollThings.cs +++ b/Logic/NN/FunUnrollThings.cs @@ -5,6 +5,8 @@ using VectorT = System.Runtime.Intrinsics.Vector256; #endif +#pragma warning disable CS0162 // Unreachable code detected + namespace Lizard.Logic.NN { public static unsafe class FunUnrollThings @@ -593,4 +595,4 @@ public static void UnrollSubtract(short* src, short* dst, short* sub1) } } -} +} \ No newline at end of file diff --git a/Logic/NN/VSize.cs b/Logic/NN/VSize.cs deleted file mode 100644 index 22691446..00000000 --- a/Logic/NN/VSize.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Lizard.Logic.NN -{ - public static class VSize - { - public const int Vector256Size = 32; - - /// == 4 - public const int Long = Vector256Size / sizeof(long); - - /// == 8 - public const int Int = Vector256Size / sizeof(int); - - /// == 16 - public const int Short = Vector256Size / sizeof(short); - - /// == 32 - public const int SByte = Vector256Size / sizeof(sbyte); - - - /// == 8 - public const int UInt = Vector256Size / sizeof(uint); - - /// == 16 - public const int UShort = Vector256Size / sizeof(ushort); - - /// == 32 - public const int Byte = Vector256Size / sizeof(byte); - } -} diff --git a/Logic/Search/SearchInformation.cs b/Logic/Search/SearchInformation.cs index e43dc045..8bf6d2a9 100644 --- a/Logic/Search/SearchInformation.cs +++ b/Logic/Search/SearchInformation.cs @@ -1,4 +1,7 @@ -namespace Lizard.Logic.Search + +#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + +namespace Lizard.Logic.Search { public struct SearchInformation { diff --git a/Logic/Search/SearchStackEntry.cs b/Logic/Search/SearchStackEntry.cs index 30ad0ece..e237ddbd 100644 --- a/Logic/Search/SearchStackEntry.cs +++ b/Logic/Search/SearchStackEntry.cs @@ -13,112 +13,22 @@ public unsafe struct SearchStackEntry { public static SearchStackEntry NullEntry = new SearchStackEntry(); - /// - /// The move that the Negamax/QSearch loop is currently on (or Move.Null for Null Move Pruning) at the current - /// - /// This is set before every recursive call to Negamax/QSearch. - /// - [FieldOffset(0)] - public Move CurrentMove; - - /// - /// When legal moves are generated, this move will be skipped. - ///

- /// This is used in Singular Extension searches to determine if every other move is significantly worse - /// than the excluded one, and if so we will look at the excluded one more deeply. - ///
- [FieldOffset(4)] - public Move Skip; - - /// - /// A pointer to a 2D array of scores (short[12][64]) for a particular move. - ///

- /// This should be updated after a move is made, and before a recursive call to Negamax/QSearch. - ///
- [FieldOffset(8)] - public PieceToHistory* ContinuationHistory; - - - - /// - /// The number of times that previous moves had their search depth extended by two. - /// - [FieldOffset(16)] - public int DoubleExtensions; - - /// - /// The number of moves made by both players thus far, which is generally the depth of the search times two. - /// - [FieldOffset(20)] - public short Ply; - - /// - /// The static evaluation for the position at the current . - /// - [FieldOffset(22)] - public short StaticEval; - - /// - /// Whether or not the side to move is in check at the current . - /// - [FieldOffset(24)] - public bool InCheck; - - /// - /// Set to true for PV/Root searches, or if is - /// and the TT entry had TTPV true when it was updated. - /// - [FieldOffset(25)] - public bool TTPV; - - /// - /// Set to true if there was an acceptable for the position at the current . - /// - [FieldOffset(26)] - public bool TTHit; - - [FieldOffset(27)] - private fixed byte _pad0[5]; - - - - /// - /// A pointer to an array of created with , which represents the current PV. - /// - /// This must be set on a per-thread basis, and before that thread's search begins. - /// - [FieldOffset(32)] - public Move* PV; - - [FieldOffset(40)] - private fixed byte _pad1[8]; - - - - /// - /// The first killer move for the current . - /// - [FieldOffset(48)] - public Move Killer0; - - /// - /// Killer0's score will be at this offset when casts it as a . - /// - [FieldOffset(52)] - private fixed byte _pad3[4]; - - /// - /// The second killer move for the current , - /// which is given Killer0's Move before Killer0 is overwritten with a new one. - /// - [FieldOffset(56)] - public Move Killer1; - - /// - /// Killer1's score will be at this offset when casts it as a . - /// - [FieldOffset(60)] - private fixed byte _pad4[4]; + [FieldOffset( 0)] public Move CurrentMove; + [FieldOffset( 4)] public Move Skip; + [FieldOffset( 8)] public PieceToHistory* ContinuationHistory; + [FieldOffset(16)] public int DoubleExtensions; + [FieldOffset(20)] public short Ply; + [FieldOffset(22)] public short StaticEval; + [FieldOffset(24)] public bool InCheck; + [FieldOffset(25)] public bool TTPV; + [FieldOffset(26)] public bool TTHit; + [FieldOffset(27)] private fixed byte _pad0[5]; + [FieldOffset(32)] public Move* PV; + [FieldOffset(40)] private fixed byte _pad1[8]; + [FieldOffset(48)] public Move Killer0; + [FieldOffset(52)] private fixed byte _pad2[4]; + [FieldOffset(56)] public Move Killer1; + [FieldOffset(60)] private fixed byte _pad3[4]; public SearchStackEntry() diff --git a/Logic/Transposition/TTCluster.cs b/Logic/Transposition/TTCluster.cs index 0d64082a..665544e5 100644 --- a/Logic/Transposition/TTCluster.cs +++ b/Logic/Transposition/TTCluster.cs @@ -11,17 +11,10 @@ namespace Lizard.Logic.Transposition [StructLayout(LayoutKind.Explicit, Size = 32)] public unsafe struct TTCluster { - [FieldOffset(0)] - private TTEntry _elem0; - - [FieldOffset(10)] - private TTEntry _elem1; - - [FieldOffset(20)] - private TTEntry _elem2; - - [FieldOffset(30)] - private fixed byte _pad[2]; + [FieldOffset( 0)] private TTEntry _elem0; + [FieldOffset(10)] private TTEntry _elem1; + [FieldOffset(20)] private TTEntry _elem2; + [FieldOffset(30)] private fixed byte _pad0[2]; /// /// Initializes the memory for this TTCluster instance. @@ -34,8 +27,8 @@ public TTCluster() _elem1 = new TTEntry(); _elem2 = new TTEntry(); - _pad[0] = (byte)':'; - _pad[1] = (byte)')'; + _pad0[0] = (byte)':'; + _pad0[1] = (byte)')'; } /// diff --git a/Logic/Transposition/TranspositionTable.cs b/Logic/Transposition/TranspositionTable.cs index c05a0185..c4c50314 100644 --- a/Logic/Transposition/TranspositionTable.cs +++ b/Logic/Transposition/TranspositionTable.cs @@ -29,17 +29,12 @@ public static unsafe class TranspositionTable /// public static byte Age = 0; - private static bool Initialized = false; - static TranspositionTable() { - if (!Initialized) - { - Initialize(SearchOptions.Hash); + Initialize(SearchOptions.Hash); - Assert(ClusterCount > MinTTClusters, - $"The TT is undersized! Needs space for {MinTTClusters} clusters but only has {ClusterCount}"); - } + Assert(ClusterCount > MinTTClusters, + $"The TT is undersized! Needs space for {MinTTClusters} clusters but only has {ClusterCount}"); } /// diff --git a/Logic/UCI/UCIClient.cs b/Logic/UCI/UCIClient.cs index 380d84b3..5f27ce85 100644 --- a/Logic/UCI/UCIClient.cs +++ b/Logic/UCI/UCIClient.cs @@ -1,4 +1,7 @@ -using System.Reflection; + +#pragma warning disable CS0162 // Unreachable code detected + +using System.Reflection; using Lizard.Logic.NN; using Lizard.Logic.Threads; @@ -12,13 +15,7 @@ public unsafe class UCIClient private SearchInformation info; private ThreadSetup setup; - private const string LogFileName = @".\ucilog.txt"; - - /// - /// If this is true, then engine instances will attempt to write to their own "ucilog_#.txt" files, - /// where # is a (hopefully) unique number for this instance. - /// - private const bool WriteToConcurrentLogs = false; + private static readonly string LogFileName = $".\\ucilog_{ProcessID}.txt"; private static Dictionary Options; @@ -68,19 +65,6 @@ public static void LogString(string s, bool newLine = true) { string fileToWrite = LogFileName; - if (IsRunningConcurrently) - { - if (WriteToConcurrentLogs) - { - fileToWrite = @".\ucilog_" + ProcessID + ".txt"; - } - else - { - // Concurrent logging off, just return here. - return; - } - } - using FileStream fs = new FileStream(fileToWrite, FileMode.Append, FileAccess.Write, FileShare.Read); using StreamWriter sw = new StreamWriter(fs); @@ -165,8 +149,8 @@ private void InputLoop() if (cmd == "quit") { - LogString("[INFO]: Exiting with code " + 1001); - Environment.Exit(1001); + LogString("[INFO]: Exiting with code " + 0); + Environment.Exit(0); } else if (cmd == "isready") { @@ -591,7 +575,7 @@ public static void ProcessUCIOptions() Options = new Dictionary(); // Get all "public static" fields, and specifically exclude constant fields (which have field.IsLiteral == true) - List? fields = typeof(SearchOptions).GetFields(BindingFlags.Public | BindingFlags.Static).Where(x => !x.IsLiteral).ToList(); + List fields = typeof(SearchOptions).GetFields(BindingFlags.Public | BindingFlags.Static).Where(x => !x.IsLiteral).ToList(); foreach (FieldInfo field in fields) { @@ -610,7 +594,7 @@ public static void ProcessUCIOptions() //SetSPSAOutputParams(); Options[nameof(Threads)].SetMinMax(1, 512); - Options[nameof(MultiPV)].SetMinMax(1, 5); + Options[nameof(MultiPV)].SetMinMax(1, 256); Options[nameof(Hash)].SetMinMax(1, 1048576); Options[nameof(SingularExtensionsMinDepth)].SetMinMax(2, 10); diff --git a/Logic/Util/FishBench.cs b/Logic/Util/FishBench.cs index 5a9900d4..b89e6dea 100644 --- a/Logic/Util/FishBench.cs +++ b/Logic/Util/FishBench.cs @@ -1,7 +1,5 @@ namespace Lizard.Logic.Util { - // SkipStaticConstructor since this introduces some unnecessary strings - [SkipStaticConstructor] public static class FishBench { private static Dictionary FENDict = new Dictionary() diff --git a/Logic/Util/SearchBench.cs b/Logic/Util/SearchBench.cs index e9306d8a..8609cf9e 100644 --- a/Logic/Util/SearchBench.cs +++ b/Logic/Util/SearchBench.cs @@ -1,6 +1,5 @@ namespace Lizard.Logic.Util { - [SkipStaticConstructor] public static class SearchBench { diff --git a/Logic/Util/SkipStaticConstructorAttribute.cs b/Logic/Util/SkipStaticConstructorAttribute.cs deleted file mode 100644 index 62903a4f..00000000 --- a/Logic/Util/SkipStaticConstructorAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Lizard.Logic.Util -{ - /// - /// Placing this attribute on a class causes the static constructor of that class to be skipped during program initialization. - /// - /// This is important for the main classes of NNUE networks as well as their FeatureTransformers, as only one - /// architecture can be used at a time, and initializing multiple wastes memory. - /// - [System.AttributeUsage(AttributeTargets.Class)] - public sealed class SkipStaticConstructorAttribute : Attribute { } -} diff --git a/Logic/Util/Utilities.cs b/Logic/Util/Utilities.cs index 9907e0a3..5858cbdb 100644 --- a/Logic/Util/Utilities.cs +++ b/Logic/Util/Utilities.cs @@ -53,21 +53,10 @@ public static class Utilities public const ulong BlackQueensideMask = (1UL << B8) | (1UL << C8) | (1UL << D8); - public const string InitialFEN = @"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - /// - /// Set to true if multiple instances of this engine are running. - ///

- /// If there are, then the log file to not be written to unless = . - ///
- public static bool IsRunningConcurrently = false; - - /// - /// The process ID for this engine instance. This is only used to determine the name of the log file to write to. - /// - public static int ProcessID; + public static int ProcessID => Environment.ProcessId; public const bool NO_LOG_FILE = true; @@ -82,75 +71,15 @@ public static void Log(string s) } else if (!NO_LOG_FILE) { +#pragma warning disable CS0162 // Unreachable code detected UCIClient.LogString("[LOG]: " + s); +#pragma warning restore CS0162 // Unreachable code detected } Debug.WriteLine(s); } - /// - /// If there are multiple instances of this engine running, we won't write to the ucilog file. - ///

- /// This uses a FileStream to access it and a mutex to make writes atomic, so having multiple - /// processes all doing that at the same time is needlessly risky. - ///
- public static void CheckConcurrency() - { - Process thisProc = Process.GetCurrentProcess(); - ProcessID = thisProc.Id; - - var selfProcs = Process.GetProcesses().Where(x => x.ProcessName == thisProc.ProcessName).ToList(); - - int concurrencyCount = 0; - int duplicates = 0; - - var thisTime = thisProc.StartTime.Ticks; - - for (int i = 0; i < selfProcs.Count; i++) - { - try - { - // Windows doesn't like you touching the "System Idle Process" (0) or "System" (4) - // and will throw an error if you try to get their MainModules, - // so in case you prefer to rename the engine binary to "Idle" this will hopefully avoid that issue :) - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && (selfProcs[i].Id == 0 || selfProcs[i].Id == 4)) - { - continue; - } - - // Ensure that the processes are exactly the same as this one. - // This checks their entire path, since multiple engine instance concern is - // only if they are started from the same path. - if (selfProcs[i].MainModule?.FileName != thisProc.MainModule?.FileName) - { - continue; - } - } - catch (Exception e) - { - // This will be a Win32Exception for trying to enumerate the modules of a SYSTEM process - concurrencyCount++; - continue; - } - - duplicates++; - - if (selfProcs[i].StartTime.Ticks < thisTime) - { - // This instance was launched after another, so increase this one's count - concurrencyCount++; - } - } - - // If this is the only instance, this should be 0 - if (concurrencyCount != 0) - { - Log("Running concurrently! (" + concurrencyCount + " other process" + (concurrencyCount > 1 ? "es" : string.Empty) + ")"); - IsRunningConcurrently = true; - } - } - public static string GetCompilerInfo() { StringBuilder sb = new StringBuilder(); @@ -166,10 +95,7 @@ public static string GetCompilerInfo() sb.Append(IsAOTAttribute.IsAOT() ? "AOT " : string.Empty); - if (HasSkipInit) - { - sb.Append("SkipInit "); - } + sb.Append(HasSkipInit ? "SkipInit " : string.Empty); sb.Append(Avx2.IsSupported ? "Avx2 " : string.Empty); @@ -190,24 +116,13 @@ public static string GetCompilerInfo() return sb.ToString(); } - public static class Direction - { - public const int NORTH = 8; - public const int EAST = 1; - public const int SOUTH = -NORTH; - public const int WEST = -EAST; - - public const int NORTH_EAST = NORTH + EAST; - public const int SOUTH_EAST = SOUTH + EAST; - public const int SOUTH_WEST = SOUTH + WEST; - public const int NORTH_WEST = NORTH + WEST; - } /// /// Returns the that the pawns move in, white pawns up, black pawns down. /// public static int ShiftUpDir(int color) => (color == Color.White) ? Direction.NORTH : Direction.SOUTH; + /// /// Returns a bitboard with bits set 1 "above" the bits in . /// So Forward(Color.White) with a bitboard that has A2 set will return one with A3 set, @@ -215,23 +130,9 @@ public static class Direction /// public static ulong Forward(int color, ulong b) { - return (color == Color.White) ? Shift(Direction.NORTH, b) : Shift(Direction.SOUTH, b); + return Shift(ShiftUpDir(color), b); } - /// - /// Returns a bitboard with bits set 1 "below" the bits in . - /// So Backward(Color.White) with a bitboard that has A2 set will return one with A1 set, - /// and Backward(Color.Black) returns one with A3 set instead. - /// - public static ulong Backward(int color, ulong b) - { - if (color == Color.White) - { - return Shift(Direction.SOUTH, b); - } - - return Shift(Direction.NORTH, b); - } /// /// Shifts the bits in in the direction . @@ -239,20 +140,19 @@ public static ulong Backward(int color, ulong b) public static ulong Shift(int dir, ulong b) { return dir == Direction.NORTH ? b << 8 - : dir == Direction.SOUTH ? b >> 8 - : dir == Direction.NORTH + Direction.NORTH ? b << 16 - : dir == Direction.SOUTH + Direction.SOUTH ? b >> 16 - : dir == Direction.EAST ? (b & ~FileHBB) << 1 - : dir == Direction.WEST ? (b & ~FileABB) >> 1 - : dir == Direction.NORTH_EAST ? (b & ~FileHBB) << 9 - : dir == Direction.NORTH_WEST ? (b & ~FileABB) << 7 - : dir == Direction.SOUTH_EAST ? (b & ~FileHBB) >> 7 - : dir == Direction.SOUTH_WEST ? (b & ~FileABB) >> 9 - : 0; + : dir == Direction.SOUTH ? b >> 8 + : dir == Direction.NORTH + Direction.NORTH ? b << 16 + : dir == Direction.SOUTH + Direction.SOUTH ? b >> 16 + : dir == Direction.EAST ? (b & ~FileHBB) << 1 + : dir == Direction.WEST ? (b & ~FileABB) >> 1 + : dir == Direction.NORTH_EAST ? (b & ~FileHBB) << 9 + : dir == Direction.NORTH_WEST ? (b & ~FileABB) << 7 + : dir == Direction.SOUTH_EAST ? (b & ~FileHBB) >> 7 + : dir == Direction.SOUTH_WEST ? (b & ~FileABB) >> 9 + : 0; } - /// /// Returns a ulong with bits set along whichever file is in. /// @@ -261,6 +161,7 @@ public static ulong GetFileBB(int idx) return FileABB << GetIndexFile(idx); } + /// /// Returns a ulong with bits set along whichever rank is on. /// @@ -270,8 +171,6 @@ public static ulong GetRankBB(int idx) } - - /// /// Returns the opposite of . /// @@ -290,10 +189,11 @@ public static string ColorToString(int color) { Color.White => nameof(Color.White), Color.Black => nameof(Color.Black), - _ => "None" + _ => "None" }; } + /// /// Returns the numerical value of the . /// @@ -303,10 +203,11 @@ public static int StringToColor(string colorName) { "white" => Color.White, "black" => Color.Black, - _ => Color.ColorNB + _ => Color.ColorNB }; } + /// /// Returns the name of the piece of type . /// @@ -314,16 +215,17 @@ public static string PieceToString(int n) { return n switch { - Piece.Pawn => nameof(Piece.Pawn), + Piece.Pawn => nameof(Piece.Pawn), Piece.Knight => nameof(Piece.Knight), Piece.Bishop => nameof(Piece.Bishop), - Piece.Rook => nameof(Piece.Rook), - Piece.Queen => nameof(Piece.Queen), - Piece.King => nameof(Piece.King), - _ => "None" + Piece.Rook => nameof(Piece.Rook), + Piece.Queen => nameof(Piece.Queen), + Piece.King => nameof(Piece.King), + _ => "None" }; } + /// /// Returns the type of the piece called . /// @@ -331,16 +233,17 @@ public static int StringToPiece(string pieceName) { return pieceName.ToLower() switch { - "pawn" => Piece.Pawn, + "pawn" => Piece.Pawn, "knight" => Piece.Knight, "bishop" => Piece.Bishop, - "rook" => Piece.Rook, - "queen" => Piece.Queen, - "king" => Piece.King, - _ => Piece.None + "rook" => Piece.Rook, + "queen" => Piece.Queen, + "king" => Piece.King, + _ => Piece.None }; } + /// /// Returns the first letter of the name of the piece of type , so PieceToFENChar(0 [Piece.Pawn]) returns 'P'. /// @@ -348,16 +251,17 @@ public static char PieceToFENChar(int pieceType) { return pieceType switch { - Piece.Pawn => 'P', + Piece.Pawn => 'P', Piece.Knight => 'N', Piece.Bishop => 'B', - Piece.Rook => 'R', - Piece.Queen => 'Q', - Piece.King => 'K', - _ => ' ' + Piece.Rook => 'R', + Piece.Queen => 'Q', + Piece.King => 'K', + _ => ' ' }; } + /// /// Returns the numerical piece type of the piece given its FEN character . /// @@ -371,12 +275,11 @@ public static int FENToPiece(char fenChar) 'r' => Piece.Rook, 'q' => Piece.Queen, 'k' => Piece.King, - _ => Piece.None + _ => Piece.None }; } - /// /// Returns a random ulong using the Random instance . /// @@ -410,21 +313,25 @@ public static bool DirectionOK(int sq, int dir) /// public static char GetFileChar(int fileNumber) => (char)(97 + fileNumber); + /// /// Returns the number of the file with the letter , so GetFileInt('a') returns 0. /// public static int GetFileInt(char fileLetter) => fileLetter - 97; + /// /// Returns the file (x coordinate) for the index, which is between A=0 and H=7. /// public static int GetIndexFile(int index) => index & 7; + /// /// Returns the rank (y coordinate) for the index, which is between 0 and 7. /// public static int GetIndexRank(int index) => index >> 3; + /// /// Sets to the file of , and to its rank. /// @@ -434,6 +341,7 @@ public static void IndexToCoord(in int index, out int x, out int y) y = index / 8; } + /// /// Returns the index of the square with the rank and file . /// @@ -442,6 +350,7 @@ public static int CoordToIndex(int x, int y) return (y * 8) + x; } + /// /// Returns the rank and file of the square , which looks like "a1" or "e4". /// @@ -460,7 +369,6 @@ public static int StringToIndex(string s) } - /// /// Returns a text representation of the board /// @@ -563,16 +471,19 @@ public static string CenteredString(string s, int width) return new string(' ', leftPadding) + s + new string(' ', rightPadding); } + public static bool EqualsIgnoreCase(this string s, string other) { return s.Equals(other, StringComparison.OrdinalIgnoreCase); } + public static bool StartsWithIgnoreCase(this string s, string other) { return s.StartsWith(other, StringComparison.OrdinalIgnoreCase); } + public static bool ContainsIgnoreCase(this string s, string other) { return s.Contains(other, StringComparison.OrdinalIgnoreCase); @@ -655,6 +566,7 @@ public static string FormatSearchInformationMultiPV(ref SearchInformation info) return sb.ToString(); } + /// /// Returns an appropriately formatted string representing the Score, which is either "cp #" or "mate #". /// @@ -681,12 +593,6 @@ public static string FormatMoveScore(int score) } } - public static int ConvertRange(int originalStart, int originalEnd, int newStart, int newEnd, int value) - { - double scale = (double)(newEnd - newStart) / (originalEnd - originalStart); - return (int)(newStart + (value - originalStart) * scale); - } - /// /// Sorts the between the starting index and last index @@ -722,11 +628,13 @@ public static void StableSort(ref List items, int offset = 0, int end = -1 } } + public static int AsInt(this bool v) { return v ? 1 : 0; } + public static bool AsBool(this int v) { return (v != 0); diff --git a/Program.cs b/Program.cs index ac54caab..08e7b360 100644 --- a/Program.cs +++ b/Program.cs @@ -43,7 +43,7 @@ public static void InitializeAll() } - Console.CancelKeyPress += delegate (object? sender, ConsoleCancelEventArgs e) { + Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e) { if (!SearchPool.StopThreads) { // If a search is ongoing, stop it instead of closing the console. @@ -65,8 +65,6 @@ public static void InitializeAll() // Give the VS debugger a friendly name for the main program thread Thread.CurrentThread.Name = "MainThread"; - Utilities.CheckConcurrency(); - // The GC seems to drag its feet collecting some of the now unneeded memory (random strings and RunClassConstructor junk). // This doesn't HAVE to be done now, and generally it isn't productive to force GC collections, // but it will inevitably occur at some point later so we can take a bit of pressure off of it by doing this now.