Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 123 additions & 94 deletions src/Lynx/Model/TranspositionTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,24 +111,32 @@ public readonly ulong CalculateTTIndex(ulong positionUniqueIdentifier, int halfM
public readonly bool ProbeHash(Position position, int halfMovesWithoutCaptureOrPawnMove, int ply, out TTProbeResult result) // [MaybeNullWhen(false)]
{
var ttIndex = CalculateTTIndex(position.UniqueIdentifier, halfMovesWithoutCaptureOrPawnMove);
var bucket = _tt[ttIndex];

var key = GenerateTTKey(position.UniqueIdentifier);

// We simply take the first entry
for (int i = 0; i < Constants.TranspositionTableElementsPerBucket; ++i)
unsafe
{
ref var entry = ref bucket[i];

if (key == entry.Key)
fixed (TranspositionTableBucket* ttPtr = _tt)
{
// We want to translate the checkmate position relative to the saved node to our root position from which we're searching
// If the recorded score is a checkmate in 3 and we are at depth 5, we want to read checkmate in 8
var recalculatedScore = RecalculateMateScores(entry.Score, ply);
var bucketPtr = ttPtr + ttIndex;
var bucket = (TranspositionTableElement*)bucketPtr;

var key = GenerateTTKey(position.UniqueIdentifier);

// We simply take the first entry
for (int i = 0; i < Constants.TranspositionTableElementsPerBucket; ++i)
{
ref var entry = ref bucket[i];

if (key == entry.Key)
{
// We want to translate the checkmate position relative to the saved node to our root position from which we're searching
// If the recorded score is a checkmate in 3 and we are at depth 5, we want to read checkmate in 8
var recalculatedScore = RecalculateMateScores(entry.Score, ply);

result = new TTProbeResult(recalculatedScore, entry.Move, entry.Type, entry.StaticEval, entry.Depth, entry.WasPv);
result = new TTProbeResult(recalculatedScore, entry.Move, entry.Type, entry.StaticEval, entry.Depth, entry.WasPv);

return true;
return true;
}
}
}
}

Expand All @@ -146,102 +154,96 @@ public readonly void RecordHash(Position position, int halfMovesWithoutCaptureOr
Debug.Assert(nodeType != NodeType.Alpha || move is null, "Assertion failed", "There's no 'best move' on fail-lows, so TT one won't be overriden");

var ttIndex = CalculateTTIndex(position.UniqueIdentifier, halfMovesWithoutCaptureOrPawnMove);
ref var bucket = ref _tt[ttIndex];

ref TranspositionTableElement entry = ref bucket[0];

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static int CalculateBucketWeight(TranspositionTableElement entry, int ttAge)
unsafe
{
// Another way of doing:
// var ageDiff = age - entry.Age
// if (ageDiff <0) ageDiff += maxAge
var relativeAge = (ttAge - entry.Age + TranspositionTableElement.MaxAge) & TranspositionTableElement.AgeMask;
fixed (TranspositionTableBucket* ttPtr = _tt)
{
var bucketPtr = ttPtr + ttIndex;
var bucket = (TranspositionTableElement*)bucketPtr;

var value = entry.Depth - (2 * relativeAge);
return value;
}
ref TranspositionTableElement entry = ref bucket[0];

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static int CalculateBucketWeight(TranspositionTableElement entry, int ttAge)
{
// Another way of doing:
// var ageDiff = age - entry.Age
// if (ageDiff <0) ageDiff += maxAge
var relativeAge = (ttAge - entry.Age + TranspositionTableElement.MaxAge) & TranspositionTableElement.AgeMask;

var value = entry.Depth - (2 * relativeAge);
return value;
}

#if DEBUG
int bucketIndex = 0;
int bucketIndex = 0;
#endif

var newKey = GenerateTTKey(position.UniqueIdentifier);
var newKey = GenerateTTKey(position.UniqueIdentifier);

if (entry.Key != newKey && entry.Type != NodeType.Unknown)
{
int minValue = CalculateBucketWeight(entry, _age);
if (entry.Key != newKey && entry.Type != NodeType.Unknown)
{
int minValue = CalculateBucketWeight(entry, _age);

for (int i = 1; i < Constants.TranspositionTableElementsPerBucket; ++i)
{
ref var candidateEntry = ref bucket[i];
for (int i = 1; i < Constants.TranspositionTableElementsPerBucket; ++i)
{
ref var candidateEntry = ref bucket[i];

// Bucket policy to discard very old entries
// Bucket policy to discard very old entries

// Always take an empty entry, or one that corresponds to the same position
if (candidateEntry.Type == NodeType.Unknown || candidateEntry.Key == newKey)
{
entry = ref candidateEntry;
// Always take an empty entry, or one that corresponds to the same position
if (candidateEntry.Type == NodeType.Unknown || candidateEntry.Key == newKey)
{
entry = ref candidateEntry;

#if DEBUG
bucketIndex = i;
bucketIndex = i;
#endif

break;
}
break;
}

// Otherwise, take the entry with the lowest weight (calculated based on depth and age)
// Current formula from Stormphrax
var value = CalculateBucketWeight(candidateEntry, _age);
// Otherwise, take the entry with the lowest weight (calculated based on depth and age)
// Current formula from Stormphrax
var value = CalculateBucketWeight(candidateEntry, _age);

if (value < minValue)
{
minValue = value;
entry = ref candidateEntry;
if (value < minValue)
{
minValue = value;
entry = ref candidateEntry;

#if DEBUG
bucketIndex = i;
bucketIndex = i;
#endif
}
}
}
}
}

var wasPvInt = wasPv ? 1 : 0;
var wasPvInt = wasPv ? 1 : 0;

// Replacement policy
bool shouldReplace =
entry.Key != newKey // Different key: collision or no actual entry
|| nodeType == NodeType.Exact // Entering PV data
|| entry.Age != _age // Different age/generation
|| depth // Higher depth
+ Configuration.EngineSettings.TTReplacement_DepthOffset
+ (Configuration.EngineSettings.TTReplacement_TTPVDepthOffset * wasPvInt)
>= entry.Depth;

if (!shouldReplace)
{
return;
}
// Replacement policy
bool shouldReplace =
entry.Key != newKey // Different key: collision or no actual entry
|| nodeType == NodeType.Exact // Entering PV data
|| entry.Age != _age // Different age/generation
|| depth // Higher depth
+ Configuration.EngineSettings.TTReplacement_DepthOffset
+ (Configuration.EngineSettings.TTReplacement_TTPVDepthOffset * wasPvInt)
>= entry.Depth;

// We want to store the distance to the checkmate position relative to the current node, independently from the root
// If the evaluated score is a checkmate in 8 and we're at depth 5, we want to store checkmate value in 3
var recalculatedScore = RecalculateMateScores(score, -ply);
if (!shouldReplace)
{
return;
}

entry.Update(newKey, recalculatedScore, staticEval, depth, nodeType, wasPvInt, move, _age);
// We want to store the distance to the checkmate position relative to the current node, independently from the root
// If the evaluated score is a checkmate in 8 and we're at depth 5, we want to store checkmate value in 3
var recalculatedScore = RecalculateMateScores(score, -ply);

#if DEBUG
Debug.Assert(bucket[bucketIndex].Score == recalculatedScore);
Debug.Assert(bucket[bucketIndex].Type == nodeType);
Debug.Assert(bucket[bucketIndex].Age == _age);
Debug.Assert(_tt[ttIndex][bucketIndex].Score == recalculatedScore);
Debug.Assert(_tt[ttIndex][bucketIndex].Type == nodeType);
Debug.Assert(bucket[bucketIndex].Age == _age);

if (_tt[ttIndex][bucketIndex].Score != recalculatedScore)
{
throw new LynxException();
entry.Update(newKey, recalculatedScore, staticEval, depth, nodeType, wasPvInt, move, _age);
}
}
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -280,12 +282,21 @@ public readonly int HashfullPermillApprox()
{
for (int i = 0; i < 1_000; ++i)
{
var bucket = _tt[i];
for (int j = 0; j < Constants.TranspositionTableElementsPerBucket; ++j)
unsafe
{
if (bucket[j].Key != default)
fixed (TranspositionTableBucket* ttPtr = _tt)
{
++items;
var bucketPtr = ttPtr + i;
var bucket = (TranspositionTableElement*)bucketPtr;

for (int j = 0; j < Constants.TranspositionTableElementsPerBucket; ++j)
{
TranspositionTableElement entry = bucket[j];
if (entry.Key != default)
{
++items;
}
}
}
}
}
Expand Down Expand Up @@ -344,12 +355,21 @@ private readonly int PopulatedItemsCount()
int items = 0;
for (int i = 0; i < _tt.Length; ++i)
{
var bucket = _tt[i];
for (int j = 0; j < Constants.TranspositionTableElementsPerBucket; ++j)
unsafe
{
if (bucket[j].Key != default)
fixed (TranspositionTableBucket* ttPtr = _tt)
{
++items;
var bucketPtr = ttPtr + i;
var bucket = (TranspositionTableElement*)bucketPtr;

for (int j = 0; j < Constants.TranspositionTableElementsPerBucket; ++j)
{
TranspositionTableElement entry = bucket[j];
if (entry.Key != default)
{
++items;
}
}
}
}
}
Expand All @@ -366,12 +386,21 @@ private readonly void Stats()
int items = 0;
for (int i = 0; i < _tt.Length; ++i)
{
var bucket = _tt[i];
for (int j = 0; j < Constants.TranspositionTableElementsPerBucket; ++j)
unsafe
{
if (bucket[j].Key != default)
fixed (TranspositionTableBucket* ttPtr = _tt)
{
++items;
var bucketPtr = ttPtr + i;
var bucket = (TranspositionTableElement*)bucketPtr;

for (int j = 0; j < Constants.TranspositionTableElementsPerBucket; ++j)
{
TranspositionTableElement entry = bucket[j];
if (entry.Key != default)
{
++items;
}
}
}
}
}
Expand Down
14 changes: 12 additions & 2 deletions src/Lynx/Model/TranspositionTableBucket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@

namespace Lynx.Model;

[InlineArray(Constants.TranspositionTableElementsPerBucket)]
/// <summary>
/// A pointer to this (TranspositionTableBucket*) can be casted to a TranspositionTableElement* and indexed from 0 to <see cref="Constants.TranspositionTableElementsPerBucket"/> - 1 to access each entry.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 30)]
public struct TranspositionTableBucket
{
#pragma warning disable S1144, RCS1213 // Unused private types or members should be removed
private TranspositionTableElement _ttEntry;
[FieldOffset(0)]
private TranspositionTableElement _ttEntry0;

[FieldOffset(10)]
private TranspositionTableElement _ttEntry1;

[FieldOffset(20)]
private TranspositionTableElement _ttEntry2;
#pragma warning restore S1144 // Unused private types or members should be removed

// TODO Add byte padding to align with 32 or 64 bytes
Expand Down
Loading