Use raw pointers for search parameters to avoid false sharing, make global tunable dictionary constant (bench 5736541)

This commit is contained in:
2025-12-16 18:20:55 +01:00
parent 4a60d0f5cc
commit 2c24d7a543
5 changed files with 35 additions and 34 deletions

View File

@@ -44,6 +44,7 @@ PATCH_VERSION := 2
THP_PAGE_ALIGNMENT := 2097152
CFLAGS := -flto -static
CUSTOM_FLAGS := -d:outputBuckets=$(OUTPUT_BUCKETS) \
-d:inputBuckets=$(INPUT_BUCKETS) \
-d:hlSize=$(HL_SIZE) \
@@ -95,8 +96,6 @@ else
CUSTOM_FLAGS += -d:danger
endif
CFLAGS := -flto -static
ifeq ($(DBG_SYMBOLS),1)
CUSTOM_FLAGS += --debugger:native
CFLAGS += -fno-omit-frame-pointer -ggdb

View File

@@ -190,7 +190,7 @@ when isMainModule:
if bench:
runBench(benchDepth)
if getParams:
echo getSPSAInput(getDefaultParameters())
echo getSPSAInput(getDefaultParameters()[])
elif magicGen:
magicWizard()
elif augment:

View File

@@ -124,7 +124,7 @@ type
state* : SearchState
statistics* : SearchStatistics
limiter* : SearchLimiter
parameters* : SearchParameters
parameters* : ptr SearchParameters
logger* : SearchLogger
histories* : HistoryTables
ttable : ptr TTable
@@ -551,14 +551,14 @@ proc scoreMove(self: SearchManager, hashMove: Move, move: Move, ply: int): Score
# Good/bad tacticals
if move.isTactical():
let winning = self.parameters.see(self.board.position, move, 0)
let winning = self.parameters[].see(self.board.position, move, 0)
if move.isCapture():
result.data += self.historyScore(sideToMove, move)
# Prioritize attacking our opponent's
# most valuable pieces
result.data += MVV_MULTIPLIER * self.parameters.staticPieceScore(self.board.on(move.targetSquare)).int32
result.data += MVV_MULTIPLIER * self.parameters[].staticPieceScore(self.board.on(move.targetSquare)).int32
elif move.isEnPassant():
result.data += MVV_MULTIPLIER * self.parameters.staticPieceScore(Pawn).int32
result.data += MVV_MULTIPLIER * self.parameters[].staticPieceScore(Pawn).int32
if not winning:
# Prioritize good exchanges (see > 0)
result.data += BAD_CAPTURE_OFFSET
@@ -723,11 +723,11 @@ proc rawEval(self: SearchManager): Score =
rooks = self.board.pieces(Rook)
queens = self.board.pieces(Queen)
let material = Score(self.parameters.materialPieceScore(Knight) * knights.count() +
self.parameters.materialPieceScore(Bishop) * bishops.count() +
self.parameters.materialPieceScore(Pawn) * pawns.count() +
self.parameters.materialPieceScore(Rook) * rooks.count() +
self.parameters.materialPieceScore(Queen) * queens.count())
let material = Score(self.parameters[].materialPieceScore(Knight) * knights.count() +
self.parameters[].materialPieceScore(Bishop) * bishops.count() +
self.parameters[].materialPieceScore(Pawn) * pawns.count() +
self.parameters[].materialPieceScore(Rook) * rooks.count() +
self.parameters[].materialPieceScore(Queen) * queens.count())
# This scales the eval linearly between base / divisor and (base + max material) / divisor
result = result * (material + Score(self.parameters.materialScalingOffset)) div Score(self.parameters.materialScalingDivisor)
@@ -863,7 +863,7 @@ proc qsearch(self: var SearchManager, root: static bool, ply: int, alpha, beta:
elif scoredMove.stage() == BadNoisy:
false
else:
self.parameters.see(self.board.position, move, 0)
self.parameters[].see(self.board.position, move, 0)
# Skip known bad captures
if not winning:
continue
@@ -873,7 +873,7 @@ proc qsearch(self: var SearchManager, root: static bool, ply: int, alpha, beta:
# Qsearch futility pruning: similar to FP in regular search, but we skip moves
# that gain no material on top of not improving alpha (given a margin)
if not recapture and not self.stack[ply].inCheck and staticEval + self.parameters.qsearchFpEvalMargin <= alpha and not self.parameters.see(self.board.position, move, 1):
if not recapture and not self.stack[ply].inCheck and staticEval + self.parameters.qsearchFpEvalMargin <= alpha and not self.parameters[].see(self.board.position, move, 1):
continue
let kingSq = self.board.position.kingSquare(self.board.sideToMove)
self.stack[ply].move = move
@@ -1201,7 +1201,7 @@ proc search(self: var SearchManager, depth, ply: int, alpha, beta: Score, isPV,
if lmrDepth <= SEE_PRUNING_MAX_DEPTH and (move.isQuiet() or move.isCapture() or move.isEnPassant()):
# SEE pruning: prune moves with a bad enough SEE score
let margin = -depth * (if move.isQuiet(): self.parameters.seePruningMargin.quiet else: self.parameters.seePruningMargin.capture)
if not self.parameters.see(self.board.position, move, margin):
if not self.parameters[].see(self.board.position, move, margin):
inc(seenMoves)
continue
var singular = 0
@@ -1523,7 +1523,7 @@ proc search*(self: var SearchManager, searchMoves: seq[Move] = @[], silent=false
for depth in 1..MAX_DEPTH:
if self.limiter.expiredSoft():
break iterativeDeepening
self.limiter.scale(self.parameters)
self.limiter.scale(self.parameters[])
for i in 1..variations:
self.statistics.selectiveDepth.store(0, moRelaxed)

View File

@@ -1326,7 +1326,10 @@ proc startUCISession* =
# Search already running. Let's teach the user a lesson
session.searcher.cancel()
searchWorker.waitFor(SearchComplete)
echo "info string premium membership is required to send go during search. Please check out https://n9x.co/heimdall-premium for details"
if not session.isMixedMode:
echo "info string premium membership is required to send go during search. Please check out https://n9x.co/heimdall-premium for details"
else:
stdout.styledWrite(useColor, fgYellow, "Warning: premium membership is required to send go during search. Please check out https://n9x.co/heimdall-premium for details\n")
continue
if session.board.isGameOver():
if not session.isMixedMode:

View File

@@ -15,6 +15,7 @@
import std/[math, tables, strutils, strformat]
import heimdall/pieces
import heimdall/util/memory/aligned
const isTuningEnabled* {.booldefine:"enableTuning".} = false
@@ -28,7 +29,7 @@ type
max*: int
default*: int
SearchParameters* = ref object
SearchParameters* = object
## A set of search parameters
# Null move pruning
@@ -110,8 +111,6 @@ type
corrHistScale*: tuple[weight, eval: tuple[pawn, nonpawn, major, minor: int]]
var params = newTable[string, TunableParameter]()
proc newTunableParameter*(name: string, min, max, default: int): TunableParameter =
## Initializes a new tunable parameter
result.name = name
@@ -179,10 +178,10 @@ MinorCorrHistEvalScale, 261
template addTunableParameter(name: string, min, max, default: int) =
params[name] = newTunableParameter(name, min, max, default)
result[name] = newTunableParameter(name, min, max, default)
proc addTunableParameters =
proc initTunableParameters: Table[string, TunableParameter] =
## Adds all our tunable parameters to the global
## parameter list
addTunableParameter("RFPBaseMargin", 1, 200, 100)
@@ -250,7 +249,10 @@ proc addTunableParameters =
if line.len() == 0:
continue
let splosh = line.split(",", maxsplit=2)
params[splosh[0]].default = splosh[1].parseInt()
result[splosh[0]].default = splosh[1].parseInt()
const params = initTunableParameters()
proc isParamName*(name: string): bool =
@@ -259,7 +261,7 @@ proc isParamName*(name: string): bool =
return name in params
proc setParameter*(self: SearchParameters, name: string, value: int) =
proc setParameter*(self: ptr SearchParameters, name: string, value: int) =
## Sets the tunable parameter with the given name
## to the given integer value
@@ -497,14 +499,13 @@ iterator getParameters*: TunableParameter =
proc getParamCount*: int = len(params)
proc getDefaultParameters*: SearchParameters {.gcsafe.} =
proc getDefaultParameters*: ptr SearchParameters {.gcsafe.} =
## Returns the set of parameters to be
## used during search
new(result)
# TODO: This is ugly, find a way around it
{.cast(gcsafe).}:
for key in params.keys():
result.setParameter(key, params[key].default)
result = allocHeapAligned(SearchParameters, 64)
result[] = default(SearchParameters)
for key in params.keys():
result.setParameter(key, params[key].default)
proc getSPSAInput*(parameters: SearchParameters): string =
@@ -519,6 +520,7 @@ proc getSPSAInput*(parameters: SearchParameters): string =
result &= "\n"
inc(i)
func staticPieceScore*(parameters: SearchParameters, kind: PieceKind): int {.inline.} =
## Returns a static score for the given piece
## type to be used inside SEE. This makes testing
@@ -546,7 +548,4 @@ func materialPieceScore*(parameters: SearchParameters, kind: PieceKind): int {.i
func materialPieceScore*(parameters: SearchParameters, piece: Piece): int {.inline.} =
## Returns a static score for the given piece
## type to be used for material scaling
return parameters.staticPieceScore(piece.kind)
addTunableParameters()
return parameters.staticPieceScore(piece.kind)