Speed up thread pool startup (bench 3475935)
This commit is contained in:
33
Dockerfile
33
Dockerfile
@@ -1,20 +1,35 @@
|
||||
FROM nimlang/nim:2.2.0-ubuntu-regular
|
||||
# By @agethereal. Thanks Andy!
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt update && apt-get -y install git clang llvm lld git-lfs
|
||||
# Set non-interactive mode to avoid prompts
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y curl git clang llvm lld build-essential libssl-dev wget && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
# Install Nim using the official installer
|
||||
RUN curl https://nim-lang.org/choosenim/init.sh -sSf | sh -s -- -y && \
|
||||
ln -s ~/.nimble/bin/nim /usr/local/bin/nim && \
|
||||
ln -s ~/.nimble/bin/nimble /usr/local/bin/nimble
|
||||
|
||||
COPY . /app/
|
||||
# Install Git-LFS
|
||||
RUN wget https://github.com/git-lfs/git-lfs/releases/download/v3.4.0/git-lfs-linux-amd64-v3.4.0.tar.gz && \
|
||||
tar -xvf git-lfs-linux-amd64-v3.4.0.tar.gz && \
|
||||
cd git-lfs-3.4.0 && \
|
||||
./install.sh && \
|
||||
git lfs install
|
||||
|
||||
# Force cache to be thrown away when new commits are pushed
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Force the cache to break if there have been new commits
|
||||
ADD https://api.github.com/repos/nocturn9x/heimdall/git/refs/heads/master /.git-hashref
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# The LFS repo is only on my personal gitea. The above cache thing still works because
|
||||
# GitHub is used as a mirror for the main repo
|
||||
RUN git clone https://git.nocturn9x.space/heimdall-engine/heimdall --depth 1 && \
|
||||
cd heimdall && make native
|
||||
cd heimdall && \
|
||||
make native
|
||||
|
||||
|
||||
CMD ["heimdall/bin/heimdall"]
|
||||
|
||||
@@ -617,6 +617,16 @@ proc isCheckmate*(self: Chessboard): bool {.inline.} =
|
||||
return moves.len() == 0
|
||||
|
||||
|
||||
proc isCheckmate*(self: var Position): bool {.inline.} =
|
||||
## Returns whether the game ended with a
|
||||
## checkmate
|
||||
if not self.inCheck():
|
||||
return false
|
||||
var moves {.noinit.} = newMoveList()
|
||||
self.generateMoves(moves)
|
||||
return moves.len() == 0
|
||||
|
||||
|
||||
proc isStalemate*(self: Chessboard): bool {.inline.} =
|
||||
## Returns whether the game ended with a
|
||||
## stalemate
|
||||
|
||||
@@ -179,6 +179,7 @@ type
|
||||
# All the heuristic tables and other state
|
||||
# to be passed to the search manager created
|
||||
# at worker setup
|
||||
evalState: EvalState # Creating this from scratch every time is VERY slow
|
||||
positions: seq[Position]
|
||||
transpositionTable: ptr TTable
|
||||
quietHistory: ptr ThreatHistoryTable
|
||||
@@ -237,68 +238,69 @@ proc newSearchManager*(positions: seq[Position], transpositions: ptr TTable,
|
||||
result = SearchManager(transpositionTable: transpositions, quietHistory: quietHistory,
|
||||
captureHistory: captureHistory, killers: killers, counters: counters,
|
||||
continuationHistory: continuationHistory, parameters: parameters,
|
||||
state: state, statistics: statistics, workerPool: createWorkerPool())
|
||||
state: state, statistics: statistics, evalState: evalState)
|
||||
new(result.board)
|
||||
if mainWorker:
|
||||
result.workerPool = createWorkerPool()
|
||||
result.limiter = newSearchLimiter(result.state, result.statistics)
|
||||
else:
|
||||
result.limiter = newDummyLimiter()
|
||||
result.evalState = evalState
|
||||
result.state.normalizeScore.store(normalizeScore)
|
||||
result.state.chess960.store(chess960)
|
||||
result.state.isMainThread.store(mainWorker)
|
||||
result.setBoardState(positions)
|
||||
if mainWorker:
|
||||
result.setBoardState(positions)
|
||||
|
||||
|
||||
|
||||
proc workerLoop(self: SearchWorker) {.thread.} =
|
||||
# Nim gets very mad indeed if we don't do this
|
||||
while true:
|
||||
let msg = self.channels.command.recv()
|
||||
case msg:
|
||||
of Ping:
|
||||
self.channels.response.send(Pong)
|
||||
of Shutdown:
|
||||
if self.isSetUp.load():
|
||||
self.isSetUp.store(false)
|
||||
freeHeapAligned(self.killers)
|
||||
freeHeapAligned(self.quietHistory)
|
||||
freeHeapAligned(self.captureHistory)
|
||||
freeHeapAligned(self.continuationHistory)
|
||||
freeHeapAligned(self.counters)
|
||||
self.channels.response.send(Ok)
|
||||
break
|
||||
of Reset:
|
||||
if not self.isSetUp.load():
|
||||
self.channels.response.send(NotSetUp)
|
||||
continue
|
||||
while true:
|
||||
let msg = self.channels.command.recv()
|
||||
case msg:
|
||||
of Ping:
|
||||
self.channels.response.send(Pong)
|
||||
of Shutdown:
|
||||
if self.isSetUp.load():
|
||||
self.isSetUp.store(false)
|
||||
freeHeapAligned(self.killers)
|
||||
freeHeapAligned(self.quietHistory)
|
||||
freeHeapAligned(self.captureHistory)
|
||||
freeHeapAligned(self.continuationHistory)
|
||||
freeHeapAligned(self.counters)
|
||||
self.channels.response.send(Ok)
|
||||
break
|
||||
of Reset:
|
||||
if not self.isSetUp.load():
|
||||
self.channels.response.send(NotSetUp)
|
||||
continue
|
||||
|
||||
resetHeuristicTables(self.quietHistory, self.captureHistory, self.killers, self.counters, self.continuationHistory)
|
||||
self.channels.response.send(Ok)
|
||||
of Go:
|
||||
# Start a search
|
||||
if not self.isSetUp.load():
|
||||
self.channels.response.send(SetupMissing)
|
||||
continue
|
||||
self.channels.response.send(Ok)
|
||||
discard self.manager.findBestLine(@[], true, false, false, 1)
|
||||
of Setup:
|
||||
if self.isSetUp.load():
|
||||
self.channels.response.send(SetupAlready)
|
||||
continue
|
||||
# Allocate on 64-byte boundaries to ensure threads won't have
|
||||
# overlapping stuff in their cache lines
|
||||
self.quietHistory = allocHeapAligned(ThreatHistoryTable, 64)
|
||||
self.continuationHistory = allocHeapAligned(ContinuationHistory, 64)
|
||||
self.captureHistory = allocHeapAligned(CaptHistTable, 64)
|
||||
self.killers = allocHeapAligned(KillersTable, 64)
|
||||
self.counters = allocHeapAligned(CountersTable, 64)
|
||||
self.isSetUp.store(true)
|
||||
self.manager = newSearchManager(self.positions, self.transpositionTable,
|
||||
self.quietHistory, self.captureHistory,
|
||||
self.killers, self.counters, self.continuationHistory,
|
||||
self.parameters, false, false)
|
||||
self.channels.response.send(Ok)
|
||||
resetHeuristicTables(self.quietHistory, self.captureHistory, self.killers, self.counters, self.continuationHistory)
|
||||
self.channels.response.send(Ok)
|
||||
of Go:
|
||||
# Start a search
|
||||
if not self.isSetUp.load():
|
||||
self.channels.response.send(SetupMissing)
|
||||
continue
|
||||
self.channels.response.send(Ok)
|
||||
discard self.manager.findBestLine(@[], true, false, false, 1)
|
||||
of Setup:
|
||||
if self.isSetUp.load():
|
||||
self.channels.response.send(SetupAlready)
|
||||
continue
|
||||
# Allocate on 64-byte boundaries to ensure threads won't have
|
||||
# overlapping stuff in their cache lines
|
||||
self.quietHistory = allocHeapAligned(ThreatHistoryTable, 64)
|
||||
self.continuationHistory = allocHeapAligned(ContinuationHistory, 64)
|
||||
self.captureHistory = allocHeapAligned(CaptHistTable, 64)
|
||||
self.killers = allocHeapAligned(KillersTable, 64)
|
||||
self.counters = allocHeapAligned(CountersTable, 64)
|
||||
self.isSetUp.store(true)
|
||||
self.manager = newSearchManager(self.positions, self.transpositionTable,
|
||||
self.quietHistory, self.captureHistory,
|
||||
self.killers, self.counters, self.continuationHistory,
|
||||
self.parameters, false, false, self.evalState)
|
||||
self.channels.response.send(Ok)
|
||||
|
||||
|
||||
proc cmd(self: SearchWorker, cmd: WorkerCommand, expected: WorkerResponse = Ok) {.inline.} =
|
||||
@@ -364,6 +366,7 @@ proc setupWorkers(self: var SearchManager) {.inline.} =
|
||||
var worker = self.workerPool.workers[i]
|
||||
# This is the only stuff that we pass from the outside
|
||||
worker.positions = self.board.positions.deepCopy()
|
||||
worker.evalState = self.evalState.deepCopy()
|
||||
worker.parameters = self.parameters
|
||||
worker.transpositionTable = self.transpositionTable
|
||||
# Keep track of worker statistics
|
||||
@@ -422,8 +425,9 @@ proc setBoardState*(self: SearchManager, state: seq[Position]) {.gcsafe.} =
|
||||
for position in state:
|
||||
self.board.positions.add(position.clone())
|
||||
self.evalState.init(self.board)
|
||||
for worker in self.workerPool.workers:
|
||||
worker.manager.setBoardState(state)
|
||||
if self.state.isMainThread.load():
|
||||
for worker in self.workerPool.workers:
|
||||
worker.manager.setBoardState(state)
|
||||
|
||||
|
||||
func getCurrentPosition*(self: SearchManager): lent Position {.inline.} =
|
||||
@@ -455,9 +459,10 @@ func stop*(self: SearchManager) {.inline.} =
|
||||
if not self.isSearching():
|
||||
return
|
||||
self.state.stop.store(true)
|
||||
# Stop all worker threads
|
||||
for child in self.workerPool.workers:
|
||||
child.manager.stop()
|
||||
if self.state.isMainThread.load():
|
||||
# Stop all worker threads
|
||||
for child in self.workerPool.workers:
|
||||
child.manager.stop()
|
||||
|
||||
|
||||
func isKillerMove(self: SearchManager, move: Move, ply: int): bool {.inline.} =
|
||||
|
||||
@@ -633,6 +633,10 @@ proc startUCISession* =
|
||||
doAssert searchWorker.channels.send.recv() == SearchComplete
|
||||
echo "info string premium membership is required to send go during search. Please check out https://n9x.co/heimdall-premium for details"
|
||||
continue
|
||||
if session.history[^1].isCheckmate():
|
||||
echo "info string position is mated"
|
||||
echo "bestmove 0000"
|
||||
continue
|
||||
# Start the clock as soon as possible to account
|
||||
# for startup delays in our time management
|
||||
session.searcher.startClock()
|
||||
|
||||
Reference in New Issue
Block a user