Skip to content

Commit 4a81600

Browse files
authored
Merge pull request #28 from boraaros/perf/solver-optimizations
Perf/solver optimizations
2 parents 0cd44f2 + 53b3404 commit 4a81600

7 files changed

Lines changed: 121 additions & 45 deletions

File tree

Alligator.Benchmark/MeasurableHeuristicTables.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ public void StoreBetaCutOff(Step move, int depth)
2727
heuristicTables.StoreBetaCutOff(move, depth);
2828
}
2929

30+
public int GetHistoryScore(Step move)
31+
{
32+
return heuristicTables.GetHistoryScore(move);
33+
}
34+
3035
public void ClearCounters()
3136
{
3237
GetKillerStepsCallCount = 0;

Alligator.Solver/Algorithms/AlphaBetaPruning.cs

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ internal class AlphaBetaPruning<TPosition, TStep> : IAlphaBetaPruning<TPosition>
88
private readonly IHeuristicTables<TStep> heuristicTables;
99
private readonly ISearchManager searchManager;
1010

11+
private const int MaxSearchDepth = 16;
12+
private readonly List<TStep>[] orderedStepBuffers;
13+
1114
public AlphaBetaPruning(
1215
IRules<TPosition, TStep> rules,
1316
ICacheTables<TPosition, TStep> cacheTables,
@@ -18,6 +21,12 @@ public AlphaBetaPruning(
1821
this.cacheTables = cacheTables ?? throw new ArgumentNullException(nameof(cacheTables));
1922
this.heuristicTables = heuristicTables ?? throw new ArgumentNullException(nameof(heuristicTables));
2023
this.searchManager = searchManager ?? throw new ArgumentNullException(nameof(searchManager));
24+
25+
orderedStepBuffers = new List<TStep>[MaxSearchDepth];
26+
for (int i = 0; i < MaxSearchDepth; i++)
27+
{
28+
orderedStepBuffers[i] = new List<TStep>();
29+
}
2130
}
2231

2332
public int Search(TPosition position, int alpha, int beta)
@@ -27,15 +36,20 @@ public int Search(TPosition position, int alpha, int beta)
2736

2837
private int SearchRecursively(TPosition position, int depth, int alpha, int beta)
2938
{
30-
if (IsLeaf(position, depth))
39+
if (depth <= 0)
3140
{
41+
if (rules.IsGoal(position))
42+
{
43+
return -(sbyte.MaxValue + depth);
44+
}
3245
return -HeuristicValue(position, depth);
3346
}
3447

3548
var originalAlpha = alpha;
3649

37-
if (cacheTables.TryGetTransposition(position, out Transposition<TStep> transposition)
38-
&& depth <= transposition.Depth)
50+
bool hasTransposition = cacheTables.TryGetTransposition(position, out Transposition<TStep> transposition);
51+
52+
if (hasTransposition && depth <= transposition.Depth)
3953
{
4054
switch (transposition.EvaluationMode)
4155
{
@@ -55,11 +69,19 @@ private int SearchRecursively(TPosition position, int depth, int alpha, int beta
5569
}
5670
}
5771

72+
var orderedSteps = GetOrderedLegalSteps(position, depth, hasTransposition, transposition.OptimalStep);
73+
74+
if (orderedSteps.Count == 0)
75+
{
76+
return -(rules.IsGoal(position) ? sbyte.MaxValue + depth : 0);
77+
}
78+
5879
var bestValue = -int.MaxValue;
5980
TStep bestStep = default;
6081

61-
foreach (var step in OrderedLegalStepsAt(position, depth, transposition))
82+
for (int i = 0; i < orderedSteps.Count; i++)
6283
{
84+
var step = orderedSteps[i];
6385
position.Take(step);
6486
var value = -SearchRecursively(position, depth - 1, -beta, -alpha);
6587
position.TakeBack();
@@ -78,40 +100,45 @@ private int SearchRecursively(TPosition position, int depth, int alpha, int beta
78100
}
79101
if (depth > 1)
80102
{
81-
EvaluationMode evaluationMode = GetEvaluationMode(bestValue, originalAlpha, beta);
82-
transposition = new Transposition<TStep>(evaluationMode, bestValue, depth, bestStep);
83-
cacheTables.AddTransposition(position, transposition);
103+
var newTransposition = new Transposition<TStep>(GetEvaluationMode(bestValue, originalAlpha, beta), bestValue, depth, bestStep);
104+
cacheTables.AddTransposition(position, newTransposition);
84105
}
85106
return bestValue;
86107
}
87108

88-
private IEnumerable<TStep> OrderedLegalStepsAt(TPosition position, int depth, Transposition<TStep> transposition)
109+
private List<TStep> GetOrderedLegalSteps(TPosition position, int depth, bool hasTransposition, TStep transpositionStep)
89110
{
90-
if (transposition != null)
91-
{
92-
yield return transposition.OptimalStep;
93-
}
94-
var prevKillerSteps = heuristicTables.GetKillerSteps(depth);
95-
var otherSteps = new List<TStep>();
111+
var result = orderedStepBuffers[depth];
112+
result.Clear();
113+
114+
var killers = heuristicTables.GetKillerSteps(depth);
115+
int killerCount = 0;
116+
bool transpositionStepIsLegal = false;
117+
96118
foreach (var move in rules.LegalStepsAt(position))
97119
{
98-
if (transposition != null && move.Equals(transposition.OptimalStep))
120+
if (hasTransposition && move.Equals(transpositionStep))
99121
{
122+
transpositionStepIsLegal = true;
100123
continue;
101124
}
102-
if (prevKillerSteps.Contains(move))
125+
if (killers.Contains(move))
103126
{
104-
yield return move;
127+
result.Insert(killerCount, move);
128+
killerCount++;
105129
}
106130
else
107131
{
108-
otherSteps.Add(move);
132+
result.Add(move);
109133
}
110134
}
111-
foreach (var move in otherSteps)
135+
136+
if (transpositionStepIsLegal)
112137
{
113-
yield return move;
138+
result.Insert(0, transpositionStep);
114139
}
140+
141+
return result;
115142
}
116143

117144
private bool IsBetaCutOff(int alpha, int beta)
@@ -124,11 +151,6 @@ private void HandleBetaCutOff(TStep step, int depth)
124151
heuristicTables.StoreBetaCutOff(step, depth);
125152
}
126153

127-
private bool IsLeaf(TPosition position, int depth)
128-
{
129-
return depth <= 0 || !rules.LegalStepsAt(position).Any();
130-
}
131-
132154
private EvaluationMode GetEvaluationMode(int value, int alpha, int beta)
133155
{
134156
if (value <= alpha)
@@ -147,16 +169,12 @@ private EvaluationMode GetEvaluationMode(int value, int alpha, int beta)
147169

148170
private int HeuristicValue(TPosition position, int depth)
149171
{
150-
if (rules.LegalStepsAt(position).Any())
172+
if (!cacheTables.TryGetValue(position, out var value))
151173
{
152-
if (!cacheTables.TryGetValue(position, out var value))
153-
{
154-
value = position.Value;
155-
cacheTables.AddValue(position, value);
156-
}
157-
return IsOpponentsTurn(depth) ? -value : value; // TODO: check if the sign is required!
174+
value = position.Value;
175+
cacheTables.AddValue(position, value);
158176
}
159-
return rules.IsGoal(position) ? sbyte.MaxValue + depth : 0;
177+
return IsOpponentsTurn(depth) ? -value : value;
160178
}
161179

162180
private bool IsOpponentsTurn(int depth)

Alligator.Solver/Algorithms/AlphaBetaSolver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public TStep OptimizeNextStep(IList<TStep> history)
5252

5353
IList<TStep> candidates = rules.LegalStepsAt(position).ToList();
5454

55-
var optimalValue = 0;
55+
var optimalValue = guess;
5656

5757
while (alpha + 1 < beta && candidates.Count > 1)
5858
{
Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,66 @@
1-
namespace Alligator.Solver.Algorithms
1+
namespace Alligator.Solver.Algorithms
22
{
33
internal class CacheTables<TPosition, TStep> : ICacheTables<TPosition, TStep>
44
where TPosition : IPosition<TStep>
55
{
6-
private readonly IDictionary<ulong, int> evaluationTable;
7-
private readonly IDictionary<ulong, Transposition<TStep>> transpositionTable;
6+
private const int TranspositionTableSize = 1 << 21;
7+
private const int TranspositionTableMask = TranspositionTableSize - 1;
8+
9+
private const int EvaluationTableSize = 1 << 23;
10+
private const int EvaluationTableMask = EvaluationTableSize - 1;
11+
12+
private const ulong KeyMarker = 0xBF58476D1CE4E5B9UL;
13+
14+
private readonly ulong[] transpositionKeys;
15+
private readonly Transposition<TStep>[] transpositionValues;
16+
17+
private readonly ulong[] evaluationKeys;
18+
private readonly int[] evaluationValues;
819

920
public CacheTables()
1021
{
11-
evaluationTable = new Dictionary<ulong, int>(10000000);
12-
transpositionTable = new Dictionary<ulong, Transposition<TStep>>(10000000);
22+
transpositionKeys = new ulong[TranspositionTableSize];
23+
transpositionValues = new Transposition<TStep>[TranspositionTableSize];
24+
evaluationKeys = new ulong[EvaluationTableSize];
25+
evaluationValues = new int[EvaluationTableSize];
1326
}
1427

1528
public void AddTransposition(TPosition position, Transposition<TStep> transposition)
1629
{
17-
transpositionTable[position.Identifier] = transposition;
30+
int index = (int)(position.Identifier & TranspositionTableMask);
31+
transpositionKeys[index] = position.Identifier ^ KeyMarker;
32+
transpositionValues[index] = transposition;
1833
}
1934

2035
public void AddValue(TPosition position, int value)
2136
{
22-
evaluationTable[position.Identifier] = value;
37+
int index = (int)(position.Identifier & EvaluationTableMask);
38+
evaluationKeys[index] = position.Identifier ^ KeyMarker;
39+
evaluationValues[index] = value;
2340
}
2441

2542
public bool TryGetTransposition(TPosition position, out Transposition<TStep> transposition)
2643
{
27-
return transpositionTable.TryGetValue(position.Identifier, out transposition);
44+
int index = (int)(position.Identifier & TranspositionTableMask);
45+
if (transpositionKeys[index] == (position.Identifier ^ KeyMarker))
46+
{
47+
transposition = transpositionValues[index];
48+
return true;
49+
}
50+
transposition = default;
51+
return false;
2852
}
2953

3054
public bool TryGetValue(TPosition position, out int value)
3155
{
32-
return evaluationTable.TryGetValue(position.Identifier, out value);
56+
int index = (int)(position.Identifier & EvaluationTableMask);
57+
if (evaluationKeys[index] == (position.Identifier ^ KeyMarker))
58+
{
59+
value = evaluationValues[index];
60+
return true;
61+
}
62+
value = 0;
63+
return false;
3364
}
3465
}
35-
}
66+
}

Alligator.Solver/Algorithms/HeuristicTables.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
internal class HeuristicTables<TMove> : IHeuristicTables<TMove>
44
{
55
private readonly IDictionary<int, IList<TMove>> killerSteps;
6+
private readonly Dictionary<TMove, int> historyScores;
67

78
private const int StoredKillerStepsLimitPerDepth = 2;
89

910
public HeuristicTables()
1011
{
1112
killerSteps = new Dictionary<int, IList<TMove>>();
13+
historyScores = new Dictionary<TMove, int>();
1214
}
1315

1416
public void StoreBetaCutOff(TMove move, int depth)
1517
{
1618
UpdateKillerSteps(move, depth);
19+
RecordHistorySuccess(move, depth);
1720
}
1821

1922
public IEnumerable<TMove> GetKillerSteps(int depth)
@@ -26,6 +29,24 @@ public IEnumerable<TMove> GetKillerSteps(int depth)
2629
return Enumerable.Empty<TMove>();
2730
}
2831

32+
public int GetHistoryScore(TMove move)
33+
{
34+
return historyScores.TryGetValue(move, out int score) ? score : 0;
35+
}
36+
37+
private void RecordHistorySuccess(TMove move, int depth)
38+
{
39+
int bonus = depth * depth;
40+
if (historyScores.TryGetValue(move, out int current))
41+
{
42+
historyScores[move] = current + bonus;
43+
}
44+
else
45+
{
46+
historyScores[move] = bonus;
47+
}
48+
}
49+
2950
private void UpdateKillerSteps(TMove move, int depth)
3051
{
3152
IList<TMove> killers;

Alligator.Solver/Algorithms/IHeuristicTables.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ internal interface IHeuristicTables<TMove>
44
{
55
IEnumerable<TMove> GetKillerSteps(int depth);
66
void StoreBetaCutOff(TMove move, int depth);
7+
int GetHistoryScore(TMove move);
78
}
89
}

Alligator.Solver/Algorithms/Transposition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace Alligator.Solver.Algorithms
22
{
3-
internal class Transposition<TStep>
3+
internal struct Transposition<TStep>
44
{
55
public EvaluationMode EvaluationMode;
66
public int Value;

0 commit comments

Comments
 (0)