Don't allocate MAX_THREADS hash tables if not necessary
authorMarco Costalba <mcostalba@gmail.com>
Sun, 24 Apr 2011 17:46:26 +0000 (18:46 +0100)
committerMarco Costalba <mcostalba@gmail.com>
Sun, 24 Apr 2011 18:23:07 +0000 (19:23 +0100)
This prevent crashing on mobile devices with limited RAM,
currently with MAX_THREADS = 32 we would need 44MB that
could be too much for a poor cellphone.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
src/material.cpp
src/search.cpp
src/thread.cpp
src/thread.h
src/tt.h

index ef7428a3a989586ceb87dfa32ce082aa08712f93..add0f32bc29509844997880cc2ffdd3aa9c67cf9 100644 (file)
@@ -85,7 +85,7 @@ namespace {
 
 /// MaterialInfoTable c'tor and d'tor allocate and free the space for Endgames
 
-void MaterialInfoTable::init() { Base::init(); funcs = new Endgames(); }
+void MaterialInfoTable::init() { Base::init(); if (!funcs) funcs = new Endgames(); }
 MaterialInfoTable::~MaterialInfoTable() { delete funcs; }
 
 
index bb61e2e9c81ab7cdf13fb6ee84e1f876ca34304a..751049cd4557a82f789aa367cbe601c771f01353 100644 (file)
@@ -435,15 +435,18 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) {
   UCIMultiPV = Options["MultiPV"].value<int>();
   SkillLevel = Options["Skill level"].value<int>();
 
-  ThreadsMgr.read_uci_options();
   read_evaluation_uci_options(pos.side_to_move());
+  ThreadsMgr.read_uci_options();
+
+  // If needed allocate pawn and material hash tables and adjust TT size
+  ThreadsMgr.init_hash_tables();
+  TT.set_size(Options["Hash"].value<int>());
 
   if (Options["Clear Hash"].value<bool>())
   {
       Options["Clear Hash"].set_value("false");
       TT.clear();
   }
-  TT.set_size(Options["Hash"].value<int>());
 
   // Do we have to play with skill handicap? In this case enable MultiPV that
   // we will use behind the scenes to retrieve a set of possible moves.
index 30c921d1808ebd4716dee58bab6deeefe0e54279..002819de566ce71a2c91e2566ca481bf1376445c 100644 (file)
@@ -64,50 +64,48 @@ void ThreadsManager::read_uci_options() {
   activeThreads           = Options["Threads"].value<int>();
 }
 
+
 // init_threads() is called during startup. Initializes locks and condition
 // variables and launches all threads sending them immediately to sleep.
 
 void ThreadsManager::init_threads() {
 
-  int i, arg[MAX_THREADS];
-  bool ok;
+  int arg[MAX_THREADS];
 
   // This flag is needed to properly end the threads when program exits
   allThreadsShouldExit = false;
 
   // Threads will sent to sleep as soon as created, only main thread is kept alive
   activeThreads = 1;
+  threads[0].state = THREAD_SEARCHING;
+
+  // Allocate pawn and material hash tables for main thread
+  init_hash_tables();
 
   lock_init(&mpLock);
 
-  for (i = 0; i < MAX_THREADS; i++)
+  // Initialize thread and split point locks
+  for (int i = 0; i < MAX_THREADS; i++)
   {
-      // Initialize thread and split point locks
       lock_init(&threads[i].sleepLock);
       cond_init(&threads[i].sleepCond);
 
       for (int j = 0; j < MAX_ACTIVE_SPLIT_POINTS; j++)
           lock_init(&(threads[i].splitPoints[j].lock));
-
-      // All threads but first should be set to THREAD_INITIALIZING
-      threads[i].state = (i == 0 ? THREAD_SEARCHING : THREAD_INITIALIZING);
-
-      // Not in Threads c'tor to avoid global initialization order issues
-      threads[i].pawnTable.init();
-      threads[i].materialTable.init();
   }
 
-  // Create and startup the threads
-  for (i = 1; i < MAX_THREADS; i++)
+  // Create and startup all the threads but the main that is already running
+  for (int i = 1; i < MAX_THREADS; i++)
   {
+      threads[i].state = THREAD_INITIALIZING;
       arg[i] = i;
 
 #if !defined(_MSC_VER)
       pthread_t pthread[1];
-      ok = (pthread_create(pthread, NULL, init_thread, (void*)(&arg[i])) == 0);
+      bool ok = (pthread_create(pthread, NULL, init_thread, (void*)(&arg[i])) == 0);
       pthread_detach(pthread[0]);
 #else
-      ok = (CreateThread(NULL, 0, init_thread, (LPVOID)(&arg[i]), 0, NULL) != NULL);
+      bool ok = (CreateThread(NULL, 0, init_thread, (LPVOID)(&arg[i]), 0, NULL) != NULL);
 #endif
       if (!ok)
       {
@@ -150,6 +148,22 @@ void ThreadsManager::exit_threads() {
 }
 
 
+// init_hash_tables() dynamically allocates pawn and material hash tables
+// according to the number of active threads. This avoids preallocating
+// memory for all possible threads if only few are used as, for instance,
+// on mobile devices where memory is scarce and allocating for MAX_THREADS
+// threads could even result in a crash.
+
+void ThreadsManager::init_hash_tables() {
+
+  for (int i = 0; i < activeThreads; i++)
+  {
+      threads[i].pawnTable.init();
+      threads[i].materialTable.init();
+  }
+}
+
+
 // cutoff_at_splitpoint() checks whether a beta cutoff has occurred in
 // the thread's currently active split point, or in some ancestor of
 // the current split point.
index e576a5c4e379017fc9283fbed4e01c7e629c3fef..e5a198c88e764902d74ff6f509c4ef4075cefbb5 100644 (file)
@@ -105,6 +105,7 @@ public:
   Thread& operator[](int threadID) { return threads[threadID]; }
   void init_threads();
   void exit_threads();
+  void init_hash_tables();
 
   int min_split_depth() const { return minimumSplitDepth; }
   int active_threads() const { return activeThreads; }
index a4ccd8be122d42d04603af957c81756ec224cde8..69cdad0c17dc19f19d351a36e0b79faf5e696700 100644 (file)
--- a/src/tt.h
+++ b/src/tt.h
@@ -148,11 +148,14 @@ struct SimpleHash {
 
   void init() {
 
+    if (entries)
+        return;
+
     entries = new (std::nothrow) Entry[HashSize];
     if (!entries)
     {
         std::cerr << "Failed to allocate " << HashSize * sizeof(Entry)
-                  << " bytes for material hash table." << std::endl;
+                  << " bytes for hash table." << std::endl;
         exit(EXIT_FAILURE);
     }
     memset(entries, 0, HashSize * sizeof(Entry));