3 Copyright (c) Scott Gasch
11 This code is the glue needed to run a parallel search. Let me
12 encourage other chess programmers to read this code carefully.
13 Implementing a parallel search is pretty easy and I procrastinated
14 doing it for WAY too long because I was afraid to sink multiple
15 months into coding it up. It's not as hard as you think. If you
16 have a good handle on multi-threaded programming then you can do
17 something that works pretty well in a week.
19 Here's my system (which is not original -- I believe this is the
20 same idea that Bruce Moreland used for Ferret and I've been told
21 it's simiar to what Bob Hyatt does in Crafty). You want to split
22 the search on all-nodes. There is some overhead involved in
23 splitting the tree and, after you paid the price, you do not want
24 one of the moves failing high... because then you have to abort
25 the split and return a fail high.
27 Once my search has one of these nodes, it calls in here to split
28 the tree. What's involved with splitting the tree is:
30 1. populating a "split node" with data about the position
32 2. grabbing zero or more idle "worker threads" to come help
34 3. all threads searching under the split grab a move from
35 the move list and search that move's subtree in parallel.
36 4. when a subtree is evaluated... all threads update the
37 split node with new information (i.e. raise alpha or, in
38 the undesirable case that a thread failed high, toggles
39 a flag in the split node to inform the rest of the
41 5. before grabbing the next move, each thread updates its
42 local alpha to pick up window-narrowing information
43 encountered by other threads.
44 6. goto step 3, stop when there are no more moves or
45 thread finds a fail high.
47 ------------------------------------------------------------------
49 This is a quote from some win32 blog about the x86 memory model
50 which I found helpful to read while I was thinking about
51 multithreaded code in this module:
53 "The most familiar model is X86. It's a relatively strong model.
54 Stores are never reordered with respect to other stores. But, in
55 the absence of data dependence, loads can be reordered with
56 respect to other loads and stores. Many X86 developers don't
57 realize that this reordering is possible, though it can lead to
58 some nasty failures under stress on big MP machines.
60 In terms of the above, the memory model for X86 can be described as:
62 1. All stores are actually store.release (upward memory fence).
63 This means that normal loads can be moved above the
64 store.release but nothing can be moved below it.
65 2. All loads are normal loads.
66 3. Any use of the LOCK prefix (e.g. LOCK CMPXCHG or LOCK INC)
67 creates a full memory fence."
75 $Id: split.c 345 2007-12-02 22:56:42Z scott $
82 extern ULONG g_uIterateDepth;
83 ULONG g_uNumHelperThreads = 0;
84 #define MAX_SPLITS (20)
85 #define IDLE ((ULONG)-1)
88 HelpSearch(SEARCHER_THREAD_CONTEXT *ctx, ULONG u);
91 // A struct that holds information about helper threads.
93 typedef struct _HELPER_THREAD
96 volatile ULONG uAssignment;
97 SEARCHER_THREAD_CONTEXT ctx;
100 UINT64 u64BusyCycles;
105 // An array of these structs that is alloced dynamically so that N
106 // cpus is not a hardcoded thing, the engine can scale N based on
107 // where it is running.
109 static HELPER_THREAD *g_HelperThreads = NULL;
112 // An array of structs that hold information about where we have split
115 static SPLIT_INFO g_SplitInfo[MAX_SPLITS];
118 // In addition to this main split database lock each split entry has
119 // its own private lock. This way contention between searcher threads
120 // operating on different split nodes is eliminated.
122 volatile static ULONG g_uSplitLock = 0;
123 #define SPLITS_LOCKED (g_uSplitLock != 0)
124 #define LOCK_SPLITS \
125 AcquireSpinLock(&g_uSplitLock); \
126 ASSERT(SPLITS_LOCKED);
127 #define UNLOCK_SPLITS \
128 ASSERT(SPLITS_LOCKED); \
129 ReleaseSpinLock(&g_uSplitLock);
131 static ULONG g_uNumSplitsAvailable;
132 volatile ULONG g_uNumHelpersAvailable;
136 HelperThreadIdleLoop(IN ULONG uMyId)
141 The entry point of a helper thread. It will spin in the idle loop
142 here until another thread splits the search tree, sees that it is
143 idle, and notifies it to come help by changing the assignment
148 ULONG uMyId : this thread's index in g_HelperThreads
156 SEARCHER_THREAD_CONTEXT *ctx = &(g_HelperThreads[uMyId].ctx);
159 ULONG uIdleLoops = 0;
167 InitializeSearcherContext(NULL, ctx);
168 ctx->uThreadNumber = uMyId + 1;
172 if (uIdleLoops == 0) u64Then = SystemReadTimeStampCounter();
175 // Did someone tell us to come help?
177 if ((u = g_HelperThreads[uMyId].uAssignment) != IDLE)
180 // By now the split info is populated.
183 ReInitializeSearcherContext(&(g_SplitInfo[u].sRootPosition), ctx);
184 ctx->pSplitInfo[0] = &(g_SplitInfo[u]);
185 ctx->uPositional = g_SplitInfo[u].uSplitPositional;
188 // Note: the main thread could have already exhausted the
189 // split and decremented it from 3->2. When we leave it
190 // will fall to 1 and allow the main thread to continue.
192 ASSERT(g_SplitInfo[u].uNumThreadsHelping >= 2);
195 // Get from the root of the search to the split position.
196 // We do this instead of just initializing the searcher
197 // context at the split node so that historic things like
198 // draw detection still work in the helper threads.
203 ASSERT(v < MAX_PLY_PER_SEARCH);
204 mv.uMove = g_SplitInfo[u].mvPathToHere[v].uMove;
205 if (ILLEGALMOVE == mv.uMove) break;
209 ASSERT(SanityCheckMove(&ctx->sPosition, mv));
212 if (FALSE == MakeMove(ctx, mv))
214 UtilPanic(CANNOT_INITIALIZE_SPLIT,
225 ASSERT(g_SplitInfo[u].uSplitPly == ctx->uPly);
227 ASSERT(IS_SAME_MOVE(g_SplitInfo[u].mvPathToHere[v-1],
228 g_SplitInfo[u].mvLast));
229 ASSERT(PositionsAreEquivalent(&(ctx->sPosition),
230 &(g_SplitInfo[u].sSplitPosition)));
231 VerifyPositionConsistency(&(ctx->sPosition), FALSE);
232 memcpy(&board, &(ctx->sPosition), sizeof(POSITION));
235 // Populate the move stack of this helper context to make
236 // it look like it generated to moves at the split. The
237 // reason I do this is so that ComputeMoveExtension (which
238 // looks at the move stack) will work at a split node even
239 // if the context it gets is a helper thread's.
241 ctx->sMoveStack.uBegin[ctx->uPly] = 0;
242 ctx->sMoveStack.uBegin[ctx->uPly + 1] =
243 ctx->sMoveStack.uEnd[ctx->uPly] = g_SplitInfo[u].uNumMoves;
246 v < g_SplitInfo[u].uNumMoves;
249 ctx->sMoveStack.mvf[v] = g_SplitInfo[u].mvf[v];
250 ASSERT(SanityCheckMove(&ctx->sPosition,
251 g_SplitInfo[u].mvf[v].mv));
255 // Go help with the search
258 ASSERT(PositionsAreEquivalent(&board, &(ctx->sPosition)));
261 // Done with this assignment, wrap up and go back to idle state
264 g_HelperThreads[uMyId].uAssignment = IDLE;
265 g_uNumHelpersAvailable++;
268 u64Now = SystemReadTimeStampCounter();
269 g_HelperThreads[uMyId].u64BusyCycles += (u64Now - u64Then);
274 // There was nothing for us to do, if that happens often
275 // enough remember the idle cycles.
280 if (uIdleLoops > 1000)
282 u64Now = SystemReadTimeStampCounter();
284 g_HelperThreads[uMyId].u64IdleCycles += (u64Now - u64Then);
288 SystemDeferExecution(1);
294 while(FALSE == g_fExitProgram);
295 Trace("HELPER THREAD: thread terminating.\n");
297 return(0); // ExitThread
302 InitializeParallelSearch(void)
307 This routine must be called before any thread can split the search
308 tree because it sets up the parallel search system.
322 if (g_Options.uNumProcessors < 2) return(FALSE);
325 // Initialize split entries
328 for (u = 0; u < MAX_SPLITS; u++)
330 memset(&(g_SplitInfo[u]), 0, sizeof(g_SplitInfo[u]));
332 g_uNumSplitsAvailable = MAX_SPLITS;
333 ASSERT(NUM_SPLIT_PTRS_IN_CONTEXT <= MAX_SPLITS);
336 // Create and initialize helper threads
338 g_uNumHelperThreads = g_Options.uNumProcessors - 1;
339 ASSERT(g_uNumHelperThreads >= 1);
340 g_HelperThreads = SystemAllocateMemory(sizeof(HELPER_THREAD) *
341 g_uNumHelperThreads);
342 ASSERT(g_HelperThreads != NULL);
343 for (u = 0; u < g_uNumHelperThreads; u++)
345 memset(&(g_HelperThreads[u]), 0, sizeof(g_HelperThreads[u]));
346 g_HelperThreads[u].uAssignment = IDLE;
347 if (FALSE == SystemCreateThread(HelperThreadIdleLoop,
349 &(g_HelperThreads[u].uHandle)))
351 UtilPanic(UNEXPECTED_SYSTEM_CALL_FAILURE,
359 g_uNumHelpersAvailable = g_uNumHelperThreads;
366 ClearHelperThreadIdleness(void)
371 Called at the start of a search, if PERF_COUNTERS is
372 defined... this function's job is to reset the idleness counter
373 for all helper threads.
388 for (u = 0; u < g_uNumHelperThreads; u++)
390 g_HelperThreads[u].u64BusyCycles =
391 g_HelperThreads[u].u64IdleCycles = 0ULL;
398 DumpHelperIdlenessReport(void)
403 Called at the end of a search, this function's job is to print
404 out a report of how busy/idle each helper threads was.
420 u < g_uNumHelperThreads;
423 n = (double)g_HelperThreads[u].u64BusyCycles;
424 d = (double)g_HelperThreads[u].u64IdleCycles;
426 Trace("Helper thread %u: %5.2f percent busy.\n", u, (n / d) * 100.0);
433 CleanupParallelSearch(void)
438 Cleanup parallel search system before program shutdown.
450 if (g_HelperThreads != NULL )
452 SystemFreeMemory(g_HelperThreads);
454 g_uNumHelperThreads = 0;
460 StartParallelSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
461 IN OUT SCORE *piAlpha,
463 IN OUT SCORE *piBestScore,
464 IN OUT MOVE *pmvBest,
466 IN INT iPositionExtend,
473 This routine is called from the main Search (not RootSearch or QSearch)
476 1. it thinks the current search tree node looks like it could
477 be searched in parallel -and-
479 2. it's likely that there are idle helper thread(s) to help.
481 It job is to find a free split node, populate it, find idle helper
482 threads, assign them to help, and search this node in parallel.
483 It _must_ be called after generating moves at this node.
487 SEARCHER_THREAD_CONTEXT *ctx : context of thread requesting split
488 SCORE *piAlpha : in/out alpha of split node
489 SCORE iBeta : in only (beta doesn't change) beta of split node
490 SCORE *piBestScore : in/out best score seen so far at split node
491 MOVE *pmvBest : in/out best move seen so far at split node
492 ULONG uMoveNum : in next move number in ctx->sMoveStack
493 INT iPositionExtend : in position based extensions for split node
494 ULONG uDepth : in the depth we need to search to
498 SCORE : the score of this split subtree, along with out params above
508 ASSERT(IS_VALID_SCORE(*piAlpha));
509 ASSERT(IS_VALID_SCORE(iBeta));
510 ASSERT(IS_VALID_SCORE(*piBestScore));
511 ASSERT(*piBestScore > -INFINITY);
512 ASSERT(pmvBest->uMove);
513 ASSERT(*piAlpha < iBeta);
515 memcpy(&board, &(ctx->sPosition), sizeof(POSITION));
516 VerifyPositionConsistency(&board, FALSE);
520 // Note: This is a lazy lock construction: search.c has peeked at
521 // g_uNumHelperThreads before calling us and only calls when it
522 // thinks there's a helper available. (1) We could find that
523 // there is no helper available now because of the race condition.
524 // (2) On IA64 memory model this type of construct is
525 // _not_supported_ (my understanding is that this is supported on
526 // X86 and AMD64, though).
529 for (u = 0; u < MAX_SPLITS; u++)
532 // Try to find a vacant split
534 if (g_SplitInfo[u].uNumThreadsHelping == 0)
537 // Found one, populate it.
539 ASSERT(g_uNumSplitsAvailable > 0);
540 g_uNumSplitsAvailable--;
543 // We initialize this to two to double-reference this
544 // split node. This guarantees we are the last ones
545 // holding a reference to it (since we want to be the last
546 // one out of this split node)
548 g_SplitInfo[u].uNumThreadsHelping = 2;
551 // Store the path from the root to the split node and the
552 // root position to start at. This is done so that
553 // ponders that convert into searches don't crash us and
554 // so that helper threads can detect repeated positions
555 // before the split point.
557 ASSERT(ctx->uPly >= 1);
558 ASSERT(ctx->uPly < MAX_PLY_PER_SEARCH);
559 for (v = 0; v < ctx->uPly; v++)
561 g_SplitInfo[u].mvPathToHere[v] = ctx->sPlyInfo[v].mv;
563 g_SplitInfo[u].mvPathToHere[v].uMove = ILLEGALMOVE;
565 g_SplitInfo[u].mvLast = g_SplitInfo[u].mvPathToHere[v-1];
566 memcpy(&(g_SplitInfo[u].sRootPosition),
570 g_SplitInfo[u].uSplitPly = ctx->uPly;
571 memcpy(&(g_SplitInfo[u].sSplitPosition),
576 // What has happened here is that another thread has
577 // triggered the "stop searching" bit in the move timer.
578 // This also means that the root position may have changed
579 // and therefore the split we just populated can be
580 // useless. Before we grab any helper threads, see if we
581 // need to bail out of this split.
583 if (g_MoveTimer.bvFlags & TIMER_STOPPING)
585 g_uNumSplitsAvailable++;
586 g_SplitInfo[u].uNumThreadsHelping = 0;
588 return(INVALID_SCORE);
592 // More split node initialization
594 g_SplitInfo[u].uLock = 0;
595 g_SplitInfo[u].fTerminate = FALSE;
596 g_SplitInfo[u].uDepth = uDepth;
597 g_SplitInfo[u].iPositionExtend = iPositionExtend;
598 g_SplitInfo[u].iAlpha = *piAlpha;
599 g_SplitInfo[u].iBeta = iBeta;
600 g_SplitInfo[u].uSplitPositional = ctx->uPositional;
601 g_SplitInfo[u].sSearchFlags = ctx->sSearchFlags;
602 ASSERT(FALSE == ctx->sSearchFlags.fAvoidNullmove);
603 g_SplitInfo[u].mvBest = *pmvBest;
604 g_SplitInfo[u].iBestScore = *piBestScore;
605 ASSERT(g_SplitInfo[u].iBestScore <= g_SplitInfo[u].iAlpha);
606 g_SplitInfo[u].sCounters.tree.u64TotalNodeCount = 0;
607 g_SplitInfo[u].sCounters.tree.u64BetaCutoffs = 0;
608 g_SplitInfo[u].sCounters.tree.u64BetaCutoffsOnFirstMove = 0;
609 g_SplitInfo[u].PV[0] = NULLMOVE;
612 // Copy the remaining moves to be searched from the
613 // searcher context that called us into the split node.
614 // Note: this thread must have already called
615 // GenerateMoves at the split node!
617 uOldStart = ctx->sMoveStack.uBegin[ctx->uPly];
618 g_SplitInfo[u].uAlreadyDone = uMoveNum - uOldStart + 1;
619 ASSERT(g_SplitInfo[u].uAlreadyDone >= 1);
620 ctx->sMoveStack.uBegin[ctx->uPly] = uMoveNum;
621 for (v = uMoveNum, g_SplitInfo[u].uRemainingMoves = 0;
622 (v < ctx->sMoveStack.uEnd[ctx->uPly]);
623 v++, g_SplitInfo[u].uRemainingMoves++)
625 ASSERT(g_SplitInfo[u].uRemainingMoves >= 0);
626 ASSERT(g_SplitInfo[u].uRemainingMoves < MAX_MOVES_PER_PLY);
629 // If we fail high at this node we have done a lot of
630 // work for naught. We also want to know as soon as
631 // possible so that we can vacate this split point,
632 // free up a worker thread and get back to the main
633 // search. So forget about the SEARCH_SORT_LIMIT
634 // stuff here and sort the whole list of moves from
637 SelectBestWithHistory(ctx, v);
638 ctx->sMoveStack.mvf[v].mv.bvFlags |=
639 WouldGiveCheck(ctx, ctx->sMoveStack.mvf[v].mv);
640 ASSERT(!(ctx->sMoveStack.mvf[v].bvFlags & MVF_MOVE_SEARCHED));
641 g_SplitInfo[u].mvf[g_SplitInfo[u].uRemainingMoves] =
642 ctx->sMoveStack.mvf[v];
644 ctx->sMoveStack.mvf[v].bvFlags |= MVF_MOVE_SEARCHED;
647 g_SplitInfo[u].uOnDeckMove = 0;
648 g_SplitInfo[u].uNumMoves = g_SplitInfo[u].uRemainingMoves;
651 v < ctx->sMoveStack.uEnd[ctx->uPly];
654 ASSERT(ctx->sMoveStack.mvf[v].bvFlags & MVF_MOVE_SEARCHED);
655 ASSERT(SanityCheckMove(&ctx->sPosition,
656 ctx->sMoveStack.mvf[v].mv));
661 // See if we can get some help here or we have to go it
662 // alone. Note: past this point the split we are using
663 // may have threads under it -- be careful.
665 for (v = 0; v < g_uNumHelperThreads; v++)
667 if (g_HelperThreads[v].uAssignment == IDLE)
670 // Note: there could already be a thread searching
671 // this split; we must obtain its lock now to mess
672 // with the helper count.
674 AcquireSpinLock(&(g_SplitInfo[u].uLock));
675 g_SplitInfo[u].uNumThreadsHelping += 1;
676 ReleaseSpinLock(&(g_SplitInfo[u].uLock));
677 ASSERT(g_SplitInfo[u].uNumThreadsHelping > 2);
678 ASSERT(g_uNumHelpersAvailable > 0);
679 g_uNumHelpersAvailable -= 1;
680 ASSERT(g_uNumHelpersAvailable >= 0);
681 ASSERT(g_uNumHelpersAvailable < g_uNumHelperThreads);
682 g_HelperThreads[v].uAssignment = u;
688 // Update the context of the thread that is initiating the
689 // split with a pointer to the split info node we are using.
692 uSplitNum < NUM_SPLIT_PTRS_IN_CONTEXT;
695 if (ctx->pSplitInfo[uSplitNum] == NULL)
697 ctx->pSplitInfo[uSplitNum] = &(g_SplitInfo[u]);
701 if (uSplitNum >= NUM_SPLIT_PTRS_IN_CONTEXT)
704 return(INVALID_SCORE);
706 ASSERT(ctx->pSplitInfo[uSplitNum] == &(g_SplitInfo[u]));
711 INC(ctx->sCounters.parallel.uNumSplits);
715 // We are done searching under this node... make sure all
716 // helpers are done too. When everyone is finished the
717 // refcount on this split node will be one because every
718 // thread decremented it once and we double referenced
721 while(g_SplitInfo[u].uNumThreadsHelping != 1)
723 ASSERT(g_SplitInfo[u].uNumThreadsHelping != 0);
724 if (g_fExitProgram) break;
728 // Note: past this point we are the only ones using the
729 // split until we return it to the pool by making its
730 // refcount zero again.
733 ASSERT((g_SplitInfo[u].uNumThreadsHelping == 1) ||
735 SystemDeferExecution(rand() % 2);
736 ASSERT((g_SplitInfo[u].uNumThreadsHelping == 1) ||
738 if (g_SplitInfo[u].iBestScore < g_SplitInfo[u].iBeta)
741 v < g_SplitInfo[u].uRemainingMoves;
744 ASSERT(g_SplitInfo[u].mvf[v].mv.uMove);
745 ASSERT(g_SplitInfo[u].mvf[v].bvFlags & MVF_MOVE_SEARCHED);
751 // Grab counters. Technically we should do with under a
752 // lock b/c we want to ensure that any pending memory
753 // operations from other cpus are flushed. But I don't
754 // really care too much about these counters and am trying
755 // to reduce lock contention.
757 if (TRUE == g_SplitInfo[u].fTerminate)
759 ASSERT(g_SplitInfo[u].iBestScore >= g_SplitInfo[u].iBeta);
760 INC(ctx->sCounters.parallel.uNumSplitsTerminated);
762 ctx->sCounters.tree.u64BetaCutoffs =
763 g_SplitInfo[u].sCounters.tree.u64BetaCutoffs;
764 ctx->sCounters.tree.u64BetaCutoffsOnFirstMove =
765 g_SplitInfo[u].sCounters.tree.u64BetaCutoffsOnFirstMove;
768 // Pop off the split info ptr from the stack in the thread's
771 ASSERT(ctx->pSplitInfo[uSplitNum] == &(g_SplitInfo[u]));
772 ctx->pSplitInfo[uSplitNum] = NULL;
775 // Grab alpha, bestscore, bestmove, PV etc... The lock
776 // needs to be here to flush any pending memory writes
777 // from other processors.
780 ctx->sCounters.tree.u64TotalNodeCount =
781 g_SplitInfo[u].sCounters.tree.u64TotalNodeCount;
782 iScore = *piBestScore = g_SplitInfo[u].iBestScore;
783 *pmvBest = g_SplitInfo[u].mvBest;
784 if ((*piAlpha < iScore) && (iScore < iBeta))
786 ASSERT(IS_SAME_MOVE(*pmvBest, g_SplitInfo[u].PV[0]));
787 ASSERT((*pmvBest).uMove != 0);
791 ASSERT((ctx->uPly + v) < MAX_PLY_PER_SEARCH);
792 ASSERT(v < MAX_PLY_PER_SEARCH);
793 ctx->sPlyInfo[ctx->uPly].PV[ctx->uPly+v] =
794 g_SplitInfo[u].PV[v];
795 if (0 == g_SplitInfo[u].PV[v].uMove) break;
800 *piAlpha = g_SplitInfo[u].iAlpha;
801 ASSERT(iBeta == g_SplitInfo[u].iBeta);
802 g_uNumSplitsAvailable++;
803 g_SplitInfo[u].uNumThreadsHelping = 0;
805 ctx->sMoveStack.uBegin[ctx->uPly] = uOldStart;
808 ASSERT(PositionsAreEquivalent(&board, &ctx->sPosition));
809 VerifyPositionConsistency(&ctx->sPosition, FALSE);
810 ASSERT(IS_VALID_SCORE(iScore) || WE_SHOULD_STOP_SEARCHING);
817 // There was no split available for us; unlock and continue in
821 return(INVALID_SCORE);
826 _UpdateSplitInfo(IN SEARCHER_THREAD_CONTEXT *ctx,
834 We think we have some information learned from search to store in
835 the split node. Take the lock and see if we really do.
839 SEARCHER_THREAD_CONTEXT *ctx : context of thread updating split
840 MOVE mv : move it just searched
841 SCORE iScore : score of the move's subtree
842 ULONG u : split node number
852 AcquireSpinLock(&(g_SplitInfo[u].uLock));
855 // See if this split is shutting down
857 if (TRUE == g_SplitInfo[u].fTerminate)
859 ReleaseSpinLock(&(g_SplitInfo[u].uLock));
863 if (iScore > g_SplitInfo[u].iBestScore)
866 // We found a move better than the best so far, so we want to
867 // update the split node and possibly raise alpha.
869 g_SplitInfo[u].iBestScore = iScore;
870 g_SplitInfo[u].mvBest = mv;
871 if (iScore > g_SplitInfo[u].iAlpha)
873 if (iScore >= g_SplitInfo[u].iBeta)
876 // We failed high so we want to update the split node
878 g_SplitInfo[u].fTerminate = TRUE;
879 g_SplitInfo[u].PV[0] = NULLMOVE;
880 ReleaseSpinLock(&(g_SplitInfo[u].uLock));
885 // Normal PV move, update the split's PV.
887 g_SplitInfo[u].iAlpha = iScore;
889 ASSERT(ctx->uPly < MAX_PLY_PER_SEARCH);
890 ASSERT(ctx->sPlyInfo[ctx->uPly].PV[ctx->uPly].uMove);
891 for (v = 0; v < MAX_PLY_PER_SEARCH; v++)
893 ASSERT((ctx->uPly + v) < MAX_PLY_PER_SEARCH);
894 mv = ctx->sPlyInfo[ctx->uPly].PV[ctx->uPly + v];
895 g_SplitInfo[u].PV[v] = mv;
896 if (0 == mv.uMove) break;
900 ReleaseSpinLock(&(g_SplitInfo[u].uLock));
905 _SetFinalStats(IN SEARCHER_THREAD_CONTEXT *ctx,
911 We are exiting the split node (because it ran out of moves or
912 because someone failed high). Update some stats on the way out.
916 SEARCHER_THREAD_CONTEXT *ctx,
926 // Before we stop searching this node, update some stuff.
928 AcquireSpinLock(&(g_SplitInfo[u].uLock));
931 // Counters to persist in the main counter struct via the split.
933 g_SplitInfo[u].sCounters.tree.u64TotalNodeCount +=
934 ctx->sCounters.tree.u64TotalNodeCount;
935 g_SplitInfo[u].sCounters.tree.u64BetaCutoffs +=
936 ctx->sCounters.tree.u64BetaCutoffs;
937 g_SplitInfo[u].sCounters.tree.u64BetaCutoffsOnFirstMove +=
938 ctx->sCounters.tree.u64BetaCutoffsOnFirstMove;
941 // TODO: Any other counters we care about?
945 // IDEA: Save the killers from this context to bring back to main
949 // Decrement threadcount in this split. Note: the main thread
950 // incremented it by two.
952 ASSERT((g_SplitInfo[u].uNumThreadsHelping > 1) || (g_fExitProgram));
953 g_SplitInfo[u].uNumThreadsHelping -= 1;
954 ASSERT((g_SplitInfo[u].uNumThreadsHelping >= 1) || (g_fExitProgram));
955 ReleaseSpinLock(&(g_SplitInfo[u].uLock));
960 _GetNextParallelMove(OUT SCORE *piAlpha,
961 OUT SCORE *piBestScore,
962 OUT ULONG *puMoveNumber,
968 Retrieve the next parallel move to search at the split node. Also
969 update alpha and bestscore.
973 SCORE *piAlpha : current alpha
974 SCORE *piBestScore : current bestscore
975 ULONG u : split number
985 AcquireSpinLock(&(g_SplitInfo[u].uLock));
986 if (g_SplitInfo[u].fTerminate)
988 ReleaseSpinLock(&(g_SplitInfo[u].uLock));
992 if (g_SplitInfo[u].uRemainingMoves != 0)
995 // There is another move to search, get it.
997 mv = g_SplitInfo[u].mvf[g_SplitInfo[u].uOnDeckMove].mv;
999 ASSERT(!(g_SplitInfo[u].mvf[g_SplitInfo[u].uOnDeckMove].bvFlags &
1000 MVF_MOVE_SEARCHED));
1001 g_SplitInfo[u].mvf[g_SplitInfo[u].uOnDeckMove].bvFlags |=
1004 ASSERT(SanityCheckMove(&g_SplitInfo[u].sSplitPosition, mv));
1006 g_SplitInfo[u].uRemainingMoves--;
1007 *puMoveNumber = g_SplitInfo[u].uOnDeckMove;
1008 g_SplitInfo[u].uOnDeckMove++;
1010 *piAlpha = g_SplitInfo[u].iAlpha;
1011 *piBestScore = g_SplitInfo[u].iBestScore;
1012 ReleaseSpinLock(&(g_SplitInfo[u].uLock));
1013 ASSERT(*piBestScore <= *piAlpha);
1019 HelpSearch(IN OUT SEARCHER_THREAD_CONTEXT *ctx,
1023 Routine description:
1025 Help search the split position.
1029 SEARCHER_THREAD_CONTEXT *ctx : thread context
1030 ULONG u : the split index to help search
1041 SCORE iBestScore = 0;
1052 memcpy(&board, &ctx->sPosition, sizeof(POSITION));
1053 ASSERT(PositionsAreEquivalent(&board, &g_SplitInfo[u].sSplitPosition));
1056 iOrigExtend = g_SplitInfo[u].iPositionExtend;
1057 iBeta = g_SplitInfo[u].iBeta;
1058 uOrigDepth = g_SplitInfo[u].uDepth;
1059 ctx->sSearchFlags = g_SplitInfo[u].sSearchFlags;
1062 iExtend = iOrigExtend;
1063 uDepth = uOrigDepth;
1064 ASSERT(ctx->uPly == g_SplitInfo[u].uSplitPly);
1066 mv = _GetNextParallelMove(&iAlpha,
1070 if (mv.uMove == 0) break; // Split is terminating
1072 ASSERT(IS_VALID_SCORE(iBestScore));
1073 ASSERT(uMoveNum < MAX_MOVES_PER_PLY);
1074 ASSERT(IS_SAME_MOVE(mv,
1075 ctx->sMoveStack.mvf[ctx->sMoveStack.uBegin[ctx->uPly]+uMoveNum].mv));
1076 ASSERT(uDepth <= MAX_DEPTH_PER_SEARCH);
1077 ASSERT(IS_VALID_SCORE(iAlpha));
1078 ASSERT(IS_VALID_SCORE(iBeta));
1079 ASSERT(iAlpha < iBeta);
1080 ASSERT(iExtend >= -ONE_PLY);
1081 ASSERT(iExtend <= +ONE_PLY);
1083 if (MakeMove(ctx, mv))
1085 ASSERT((IS_CHECKING_MOVE(mv) &&
1086 InCheck(&ctx->sPosition, ctx->sPosition.uToMove)) ||
1087 (!IS_CHECKING_MOVE(mv) &&
1088 !InCheck(&ctx->sPosition, ctx->sPosition.uToMove)));
1089 iRoughEval = GetRoughEvalScore(ctx, iAlpha, iBeta, TRUE);
1091 // Compute extension
1092 ComputeMoveExtension(ctx,
1095 (ctx->sMoveStack.uBegin[ctx->uPly - 1] +
1102 // Decide whether to history prune
1104 if (TRUE == WeShouldDoHistoryPruning(iRoughEval,
1109 (g_SplitInfo[u].uAlreadyDone +
1112 (g_SplitInfo[u].uAlreadyDone +
1116 ASSERT(iExtend == 0);
1118 ctx->sPlyInfo[ctx->uPly].iExtensionAmount = -ONE_PLY;
1122 // Compute next depth
1124 uDepth = uDepth - ONE_PLY + iExtend;
1125 if (uDepth >= MAX_DEPTH_PER_SEARCH) uDepth = 0;
1127 iScore = -Search(ctx, -iAlpha - 1, -iAlpha, uDepth);
1128 if ((iAlpha < iScore) && (iScore < iBeta))
1130 iScore = -Search(ctx, -iBeta, -iAlpha, uDepth);
1134 // Decide whether to research reduced branches to full depth.
1136 if ((iExtend < 0) && (iScore >= iBeta))
1139 ctx->sPlyInfo[ctx->uPly].iExtensionAmount = 0;
1140 iScore = -Search(ctx, -iBeta, -iAlpha, uDepth);
1142 UnmakeMove(ctx, mv);
1143 ASSERT(PositionsAreEquivalent(&ctx->sPosition, &board));
1144 if (TRUE == g_SplitInfo[u].fTerminate) break;
1145 if (iScore > iBestScore)
1147 _UpdateSplitInfo(ctx, mv, iScore, u);
1152 _SetFinalStats(ctx, u);