Stockfish 1.8 tweaks

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: Stockfish 1.8 tweaks

Post by mcostalba »

QED wrote: So what were the results? From the point of view of original stockfish: +199 =609 -192 (LOS=57:42), +212 =573 -215 (46:53), +177 =628 -195 (26:73) and +204 =614 -182 (76:23).
This is a very serious attempt to qualify the test framework ! Congrats ! Even we even didn't do something like this, we qualified 1'+0 simply because it seems to work, we made some attempts at 10" then 20" then 30" but without a full testing framework validation on some known patch.

We have found not reliable results below 20" for game (on a QUAD) and we are still investigating how it goes with 30" on QUAD.
QED
Posts: 60
Joined: Thu Nov 05, 2009 9:53 pm

Re: Stockfish 1.8 tweaks

Post by QED »

Another tweak. Test was unsuccessfull, bayeselo says original stockfish is better with LOS=87:12. :(

But it was a lot of work, and I would say there are few bugs left hurting performance, so I am going to post the patch anayway, so you can tell me why is it not working better.

Idea is simple, make transposition table store both lower bound and upper bound with their respective depths. I had to squeeze all that into TTEntry and modify search to use new interface the right way. Implementation is of course a mess. Affected files: value.h, tt.h, tt.cpp and search.cpp

I have polished few thing when the test was running, but here is the tested patch:

Code: Select all

diff -dur src-Ch/search.cpp src-BbCh/search.cpp
--- src-Ch/search.cpp   2010-07-09 13:04:18.000000000 +0200
+++ src-BbCh/search.cpp 2010-07-20 22:24:14.000000000 +0200
@@ -1113,10 +1113,13 @@
     if (!PvNode && tte && ok_to_use_TT(tte, depth, beta, ply))
     {
         // Refresh tte entry to avoid aging
-        TT.store(posKey, tte->value(), tte->type(), tte->depth(), ttMove, tte->static_value(), tte->king_danger());
+        TT.refresh&#40;*const_cast<TTEntry*>&#40;tte&#41;);

         ss->currentMove = ttMove; // Can be MOVE_NONE
-        return value_from_tt&#40;tte->value&#40;), ply&#41;;
+        if &#40;is_lower_bound&#40;tte->type&#40;)) && value_from_tt&#40;tte->lowerbound&#40;), ply&#41; >= beta&#41;
+            return value_from_tt&#40;tte->lowerbound&#40;), ply&#41;;
+        else
+            return value_from_tt&#40;tte->upperbound&#40;), ply&#41;;
     &#125;

     // Step 5. Evaluate the position statically
@@ -1148,7 +1151,7 @@
     &#123;
         // Pass ss->eval to qsearch&#40;) and avoid an evaluate call
         if (!tte || tte->static_value&#40;) == VALUE_NONE&#41;
-            TT.store&#40;posKey, ss->eval, VALUE_TYPE_EXACT, Depth&#40;-127*OnePly&#41;, MOVE_NONE, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);
+            TT.store&#40;posKey, MOVE_NONE, VALUE_TYPE_BOTH, Depth&#40;-128&#41;, Depth&#40;-128&#41;, -VALUE_INFINITE, VALUE_INFINITE, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);

         Value rbeta = beta - razor_margin&#40;depth&#41;;
         Value v = qsearch<NonPV>&#40;pos, ss, rbeta-1, rbeta, Depth&#40;0&#41;, ply&#41;;
@@ -1262,7 +1265,7 @@
                            && tte && tte->move&#40;)
                            && !excludedMove // Do not allow recursive singular extension search
                            && is_lower_bound&#40;tte->type&#40;))
-                           && tte->depth&#40;) >= depth - 3 * OnePly;
+                           && tte->lowerdepth&#40;) >= depth - 3 * OnePly;

     // Step 10. Loop through moves
     // Loop through all legal moves until no moves remain or a beta cutoff occurs
@@ -1288,7 +1291,7 @@
           && move == tte->move&#40;)
           && ext < OnePly&#41;
       &#123;
-          Value ttValue = value_from_tt&#40;tte->value&#40;), ply&#41;;
+          Value ttValue = value_from_tt&#40;tte->lowerbound&#40;), ply&#41;;

           if &#40;abs&#40;ttValue&#41; < VALUE_KNOWN_WIN&#41;
           &#123;
@@ -1442,9 +1445,16 @@
     if &#40;AbortSearch || TM.thread_should_stop&#40;threadID&#41;)
         return bestValue;

-    ValueType f = &#40;bestValue <= oldAlpha ? VALUE_TYPE_UPPER &#58; bestValue >= beta ? VALUE_TYPE_LOWER &#58; VALUE_TYPE_EXACT&#41;;
-    move = &#40;bestValue <= oldAlpha ? MOVE_NONE &#58; ss->bestMove&#41;;
-    TT.store&#40;posKey, value_to_tt&#40;bestValue, ply&#41;, f, depth, move, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);
+    if &#40;bestValue <= oldAlpha&#41;
+        TT.store&#40;posKey, MOVE_NONE, VALUE_TYPE_UPPER, Depth&#40;-128&#41;, depth, -VALUE_INFINITE,
+                 value_to_tt&#40;bestValue, ply&#41;, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);
+    else if &#40;bestValue >= beta&#41;
+        TT.store&#40;posKey, ss->bestMove, VALUE_TYPE_LOWER, depth, Depth&#40;-128&#41;, value_to_tt&#40;bestValue, ply&#41;,
+             VALUE_INFINITE, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);
+    else
+        TT.store&#40;posKey, ss->bestMove, VALUE_TYPE_EXACT, depth, depth, value_to_tt&#40;bestValue, ply&#41;,
+                 value_to_tt&#40;bestValue, ply&#41;, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);
+    move = ss->bestMove;

     // Update killers and history only for non capture moves that fails high
     if &#40;bestValue >= beta&#41;
@@ -1501,7 +1511,10 @@
     if (!PvNode && tte && ok_to_use_TT&#40;tte, depth, beta, ply&#41;)
     &#123;
         ss->currentMove = ttMove; // Can be MOVE_NONE
-        return value_from_tt&#40;tte->value&#40;), ply&#41;;
+        if &#40;is_lower_bound&#40;tte->type&#40;)) && value_from_tt&#40;tte->lowerbound&#40;), ply&#41; >= beta&#41;
+            return value_from_tt&#40;tte->lowerbound&#40;), ply&#41;;
+        else
+            return value_from_tt&#40;tte->upperbound&#40;), ply&#41;;
     &#125;

     isCheck = pos.is_check&#40;);
@@ -1529,7 +1542,8 @@
         if &#40;bestValue >= beta&#41;
         &#123;
             if (!tte&#41;
-                TT.store&#40;pos.get_key&#40;), value_to_tt&#40;bestValue, ply&#41;, VALUE_TYPE_LOWER, Depth&#40;-127*OnePly&#41;, MOVE_NONE, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);
+                TT.store&#40;pos.get_key&#40;), MOVE_NONE, VALUE_TYPE_LOWER, Depth&#40;-2&#41;, Depth&#40;-128&#41;,
+                         value_to_tt&#40;bestValue, ply&#41;, VALUE_INFINITE, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);

             return bestValue;
         &#125;
@@ -1625,8 +1639,15 @@

     // Update transposition table
     Depth d = &#40;depth == Depth&#40;0&#41; ? Depth&#40;0&#41; &#58; Depth&#40;-1&#41;);
-    ValueType f = &#40;bestValue <= oldAlpha ? VALUE_TYPE_UPPER &#58; bestValue >= beta ? VALUE_TYPE_LOWER &#58; VALUE_TYPE_EXACT&#41;;
-    TT.store&#40;pos.get_key&#40;), value_to_tt&#40;bestValue, ply&#41;, f, d, ss->bestMove, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);
+    if &#40;bestValue <= oldAlpha&#41;
+        TT.store&#40;pos.get_key&#40;), ss->bestMove, VALUE_TYPE_UPPER, Depth&#40;-128&#41;, d, -VALUE_INFINITE,
+                 value_to_tt&#40;bestValue, ply&#41;, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);
+    else if &#40;bestValue >= beta&#41;
+        TT.store&#40;pos.get_key&#40;), ss->bestMove, VALUE_TYPE_LOWER, d, Depth&#40;-128&#41;, value_to_tt&#40;bestValue, ply&#41;,
+             VALUE_INFINITE, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);
+    else
+        TT.store&#40;pos.get_key&#40;), ss->bestMove, VALUE_TYPE_EXACT, d, d, value_to_tt&#40;bestValue, ply&#41;,
+                 value_to_tt&#40;bestValue, ply&#41;, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);

     // Update killers only for checking moves that fails high
     if (    bestValue >= beta
@@ -2001,14 +2022,17 @@

   bool ok_to_use_TT&#40;const TTEntry* tte, Depth depth, Value beta, int ply&#41; &#123;

-    Value v = value_from_tt&#40;tte->value&#40;), ply&#41;;
-
-    return   (   tte->depth&#40;) >= depth
-              || v >= Max&#40;value_mate_in&#40;PLY_MAX&#41;, beta&#41;
-              || v < Min&#40;value_mated_in&#40;PLY_MAX&#41;, beta&#41;)
+    Value lb = value_from_tt&#40;tte->lowerbound&#40;), ply&#41;;
+    Value ub = value_from_tt&#40;tte->upperbound&#40;), ply&#41;;

-          && (   &#40;is_lower_bound&#40;tte->type&#40;)) && v >= beta&#41;
-              || &#40;is_upper_bound&#40;tte->type&#40;)) && v < beta&#41;);
+    return   (   is_lower_bound&#40;tte->type&#40;))
+              && (   depth <= tte->lowerdepth&#40;)
+                  || lb >= value_mate_in&#40;PLY_MAX&#41;)
+              && lb >= beta&#41;
+          || (   is_upper_bound&#40;tte->type&#40;))
+              && (   depth <= tte->upperdepth&#40;)
+                  || ub < value_mated_in&#40;PLY_MAX&#41;)
+              && ub < beta&#41;;
   &#125;


@@ -2020,11 +2044,13 @@
       if (!tte&#41;
           return defaultEval;

-      Value v = value_from_tt&#40;tte->value&#40;), ply&#41;;
+      Value lb = value_from_tt&#40;tte->lowerbound&#40;), ply&#41;;
+      Value ub = value_from_tt&#40;tte->upperbound&#40;), ply&#41;;

-      if (   &#40;is_lower_bound&#40;tte->type&#40;)) && v >= defaultEval&#41;
-          || &#40;is_upper_bound&#40;tte->type&#40;)) && v < defaultEval&#41;)
-          return v;
+      if &#40;is_lower_bound&#40;tte->type&#40;)) && lb >= defaultEval&#41;
+          return lb;
+      if &#40;is_upper_bound&#40;tte->type&#40;)) && ub < defaultEval&#41;
+          return ub;

       return defaultEval;
   &#125;
diff -dur src-Ch/tt.cpp src-BbCh/tt.cpp
--- src-Ch/tt.cpp       2010-07-09 13&#58;04&#58;18.000000000 +0200
+++ src-BbCh/tt.cpp     2010-07-20 22&#58;24&#58;14.000000000 +0200
@@ -98,22 +98,51 @@
 /// is bigger than the depth of t2. A TTEntry of type VALUE_TYPE_EVAL
 /// never replaces another entry for the same position.

-void TranspositionTable&#58;&#58;store&#40;const Key posKey, Value v, ValueType t, Depth d, Move m, Value statV, Value kingD&#41; &#123;
+/// Now TTEntry stores both upper bound and lower bound with respective depths
+/// and store return Depth != -128 when it detects conflict with stored bound.
+/// The returned depth would be a hint for search to resolve conflict.

-  int c1, c2, c3;
+void TranspositionTable&#58;&#58;store&#40;const Key posKey, Move m, ValueType t, Depth ld, Depth ud, Value lb, Value ub, Value sv, Value kd&#41; &#123;
+
+  int c1, c2, c3, c4;
   TTEntry *tte, *replace;
   uint32_t posKey32 = posKey >> 32; // Use the high 32 bits as key

+  assert&#40;t != VALUE_TYPE_NONE&#41;;
+  assert&#40;t != VALUE_TYPE_EXACT || &#40;m != MOVE_NONE&#41;);
+  assert&#40;lb <= ub&#41;;
   tte = replace = first_entry&#40;posKey&#41;;
   for &#40;int i = 0; i < ClusterSize; i++, tte++)
   &#123;
       if (!tte->key&#40;) || tte->key&#40;) == posKey32&#41; // empty or overwrite old
       &#123;
-          // Preserve any exsisting ttMove
+          // Preserve any existing ttMove, StatV or kingD.
           if &#40;m == MOVE_NONE&#41;
               m = tte->move&#40;);
+          if &#40;sv == VALUE_NONE&#41;
+              sv = tte->static_value&#40;);
+          if &#40;kd == VALUE_NONE&#41;
+              kd = tte->king_danger&#40;);

-          tte->save&#40;posKey32, v, t, d, m, generation, statV, kingD&#41;;
+          // Also, preserve compatible bounds.
+          if (   lb == -VALUE_INFINITE
+              && is_lower_bound&#40;tte->type&#40;))
+              && ub >= tte->lowerbound&#40;))
+          &#123;
+              ld = tte->lowerdepth&#40;);
+              lb = tte->lowerbound&#40;);
+              t = &#40;t == VALUE_TYPE_EXACT&#41; ? VALUE_TYPE_EXACT &#58; VALUE_TYPE_BOTH;
+          &#125;
+          if (   ub == VALUE_INFINITE
+              && is_upper_bound&#40;tte->type&#40;))
+              && lb <= tte->upperbound&#40;))
+          &#123;
+              ud = tte->upperdepth&#40;);
+              ub = tte->upperbound&#40;);
+              t = &#40;t == VALUE_TYPE_EXACT&#41; ? VALUE_TYPE_EXACT &#58; VALUE_TYPE_BOTH;
+          &#125;
+
+          tte->save&#40;posKey32, m, t, generation, ld, ud, lb, ub, sv, kd&#41;;
           return;
       &#125;

@@ -122,12 +151,14 @@

       c1 = &#40;replace->generation&#40;) == generation ?  2 &#58; 0&#41;;
       c2 = &#40;tte->generation&#40;) == generation ? -2 &#58; 0&#41;;
-      c3 = &#40;tte->depth&#40;) < replace->depth&#40;) ?  1 &#58; 0&#41;;
+      c3 = &#40;Max&#40;tte->lowerdepth&#40;), tte->upperdepth&#40;)) < Max&#40;replace->lowerdepth&#40;), replace->upperdepth&#40;)) ? 1 &#58; 0&#41;;
+      c4 = &#40;Max&#40;tte->lowerdepth&#40;), tte->upperdepth&#40;)) == Max&#40;replace->lowerdepth&#40;), replace->upperdepth&#40;))
+            ? &#40;Min&#40;tte->lowerdepth&#40;), tte->upperdepth&#40;)) < Min&#40;replace->lowerdepth&#40;), replace->upperdepth&#40;)) ? 1 &#58; 0&#41; &#58; 0&#41;;

-      if &#40;c1 + c2 + c3 > 0&#41;
+      if &#40;c1 + c2 + c3 + c4 > 0&#41;
           replace = tte;
   &#125;
-  replace->save&#40;posKey32, v, t, d, m, generation, statV, kingD&#41;;
+  replace->save&#40;posKey32, m, t, generation, ld, ud, lb, ub, sv, kd&#41;;
   overwrites++;
 &#125;

@@ -156,7 +187,7 @@

 void TranspositionTable&#58;&#58;new_search&#40;) &#123;

-  generation++;
+  generation = &#40;generation + 1&#41; & 0x0F;
   overwrites = 0;
 &#125;

@@ -175,7 +206,7 @@
   &#123;
       TTEntry *tte = retrieve&#40;p.get_key&#40;));
       if (!tte || tte->move&#40;) != pv&#91;i&#93;)
-          store&#40;p.get_key&#40;), VALUE_NONE, VALUE_TYPE_NONE, Depth&#40;-127*OnePly&#41;, pv&#91;i&#93;, VALUE_NONE, VALUE_NONE&#41;;
+          store&#40;p.get_key&#40;), pv&#91;i&#93;, VALUE_TYPE_EXACT, Depth&#40;-128&#41;, Depth&#40;-128&#41;, -VALUE_INFINITE, VALUE_INFINITE, VALUE_NONE, VALUE_NONE&#41;;
       p.do_move&#40;pv&#91;i&#93;, st&#41;;
   &#125;
 &#125;
@@ -203,7 +234,6 @@
   // get a ponder move, that's the reason of ply < 2 conditions.
   while (   &#40;tte = retrieve&#40;p.get_key&#40;))) != NULL
          && tte->move&#40;) != MOVE_NONE
-         && &#40;tte->type&#40;) == VALUE_TYPE_EXACT || ply < 2&#41;
          && move_is_legal&#40;p, tte->move&#40;))
          && (!p.is_draw&#40;) || ply < 2&#41;
          && ply < PLY_MAX&#41;
diff -dur src-Ch/tt.h src-BbCh/tt.h
--- src-Ch/tt.h 2010-07-09 13&#58;03&#58;34.000000000 +0200
+++ src-BbCh/tt.h       2010-07-20 22&#58;24&#58;14.000000000 +0200
@@ -36,49 +36,65 @@

 /// The TTEntry class is the class of transposition table entries
 ///
-/// A TTEntry needs 96 bits to be stored
-///
-/// bit  0-31&#58; key
-/// bit 32-63&#58; data
-/// bit 64-79&#58; value
-/// bit 80-95&#58; depth
+/// A TTEntry needs 128 bits to be stored
 ///
-/// the 32 bits of the data field are so defined
+/// bit   0- 31&#58; key
+/// bit  32- 48&#58; move
+/// bit  49- 51&#58; value type
+/// bit  52- 55&#58; generation
+/// bit  56- 63&#58; depth of lowerbound &#40;signed&#41;
+/// bit  64- 71&#58; depth of uperbound &#40;signed&#41;
+/// bit  72- 85&#58; lowerbound &#40;signed, shifted&#41;
+/// bit  86- 99&#58; upperbound &#40;signed, shifted&#41;
+/// bit 100-113&#58; static value &#40;signed, shifted&#41;
+/// bit 114-127&#58; king danger &#40;signed, shifted&#41;
 ///
-/// bit  0-16&#58; move
-/// bit 17-19&#58; not used
-/// bit 20-22&#58; value type
-/// bit 23-31&#58; generation
+/// works correctly for GrainSize >= 4

 class TTEntry &#123;

 public&#58;
-  void save&#40;uint32_t k, Value v, ValueType t, Depth d, Move m, int g, Value statV, Value kd&#41; &#123;
+  void save&#40;uint32_t k, Move m, ValueType t, uint8_t g, Depth ld, Depth ud, Value lb, Value ub, Value sv, Value kd&#41; &#123;

       key32 = k;
-      data = &#40;m & 0x1FFFF&#41; | &#40;t << 20&#41; | &#40;g << 23&#41;;
-      value16     = int16_t&#40;v&#41;;
-      depth16     = int16_t&#40;d&#41;;
-      staticValue = int16_t&#40;statV&#41;;
-      kingDanger  = int16_t&#40;kd&#41;;
+      move16 = uint32_t&#40;m&#41; >> 1;
+      mtg8 = (&#40;uint16_t&#40;m&#41; & 1&#41; << 7&#41; | &#40;uint16_t&#40;t&#41; << 4&#41; | g;
+      lowerdepth8 = ld;
+      upperdepth8 = ud;
+      data1_8 = uint16_t&#40;int&#40;lb&#41; + 0x8000&#41; >> 8;
+      data2_8 = &#40;uint16_t&#40;int&#40;lb&#41; + 0x8000&#41; & 0x00FC&#41; | &#40;uint16_t&#40;int&#40;ub&#41; + 0x8000&#41; >> 14&#41;;
+      data3_8 = &#40;uint16_t&#40;int&#40;ub&#41; + 0x8000&#41; & 0x3FC0&#41; >> 6;
+      data4_8 = (&#40;uint16_t&#40;int&#40;ub&#41; + 0x8000&#41; & 0x003C&#41; << 2&#41; | &#40;uint16_t&#40;int&#40;sv&#41; + 0x8000&#41; >> 12&#41;;
+      data5_8 = &#40;uint16_t&#40;int&#40;sv&#41; + 0x8000&#41; & 0x0FF0&#41; >> 4;
+      data6_8 = (&#40;uint16_t&#40;int&#40;sv&#41; + 0x8000&#41; & 0x000C&#41; << 4&#41; | &#40;uint16_t&#40;int&#40;kd&#41; + 0x8000&#41; >> 10&#41;;
+      data7_8 = &#40;uint16_t&#40;int&#40;kd&#41; + 0x8000&#41; & 0x03FC&#41; >> 2;
   &#125;
+  void set_generation&#40;uint8_t g&#41; &#123; mtg8 = &#40;mtg8 & 0xF0&#41; | g; &#125;

   uint32_t key&#40;) const &#123; return key32; &#125;
-  Depth depth&#40;) const &#123; return Depth&#40;depth16&#41;; &#125;
-  Move move&#40;) const &#123; return Move&#40;data & 0x1FFFF&#41;; &#125;
-  Value value&#40;) const &#123; return Value&#40;value16&#41;; &#125;
-  ValueType type&#40;) const &#123; return ValueType&#40;&#40;data >> 20&#41; & 7&#41;; &#125;
-  int generation&#40;) const &#123; return data >> 23; &#125;
-  Value static_value&#40;) const &#123; return Value&#40;staticValue&#41;; &#125;
-  Value king_danger&#40;) const &#123; return Value&#40;kingDanger&#41;; &#125;
+  Move move&#40;) const &#123; return Move&#40;&#40;uint32_t&#40;move16&#41; << 1&#41; | &#40;mtg8 >> 7&#41;) ; &#125;
+  ValueType type&#40;) const &#123; return ValueType&#40;&#40;mtg8 & 0x70&#41; >> 4&#41;; &#125;
+  uint8_t generation&#40;) const &#123; return mtg8 & 0x0F; &#125;
+  Depth lowerdepth&#40;) const &#123; return Depth&#40;lowerdepth8&#41;; &#125;
+  Depth upperdepth&#40;) const &#123; return Depth&#40;upperdepth8&#41;; &#125;
+  Value lowerbound&#40;) const &#123; return Value&#40;&#40;int&#40;uint32_t&#40;data1_8&#41; << 8&#41; | &#40;data2_8 & 0xFC&#41;) - 0x8000&#41;; &#125;
+  Value upperbound&#40;) const &#123; return Value&#40;&#40;int&#40;uint32_t&#40;data2_8 & 0x03&#41; << 14&#41; | &#40;uint16_t&#40;data3_8&#41; << 6&#41; | (&#40;data4_8 & 0xF0&#41; >> 2&#41;) - 0x8000&#41;; &#125;
+  Value static_value&#40;) const &#123; return Value&#40;&#40;int&#40;uint32_t&#40;data4_8 & 0x0F&#41; << 12&#41; | &#40;uint16_t&#40;data5_8&#41; << 4&#41; | (&#40;data6_8 & 0xC0&#41; >> 4&#41;) - 0x8000&#41;; &#125;
+  Value king_danger&#40;) const &#123; return Value&#40;&#40;int&#40;uint32_t&#40;data6_8 & 0x3F&#41; << 10&#41; | &#40;uint16_t&#40;data7_8&#41; << 2&#41;) - 0x8000&#41;; &#125;

 private&#58;
   uint32_t key32;
-  uint32_t data;
-  int16_t value16;
-  int16_t depth16;
-  int16_t staticValue;
-  int16_t kingDanger;
+  uint16_t move16;
+  uint8_t mtg8; // Move, value Type and Generation
+  int8_t lowerdepth8;
+  int8_t upperdepth8;
+  uint8_t data1_8;
+  uint8_t data2_8;
+  uint8_t data3_8;
+  uint8_t data4_8;
+  uint8_t data5_8;
+  uint8_t data6_8;
+  uint8_t data7_8;
 &#125;;


@@ -106,7 +122,8 @@
   ~TranspositionTable&#40;);
   void set_size&#40;size_t mbSize&#41;;
   void clear&#40;);
-  void store&#40;const Key posKey, Value v, ValueType type, Depth d, Move m, Value statV, Value kingD&#41;;
+  void store&#40;const Key posKey, Move m, ValueType t, Depth ld, Depth ud, Value lb, Value ub, Value sv, Value kd&#41;;
+  void refresh&#40;TTEntry& e&#41; &#123;e.set_generation&#40;generation&#41;; &#125;
   TTEntry* retrieve&#40;const Key posKey&#41; const;
   void new_search&#40;);
   void insert_pv&#40;const Position& pos, Move pv&#91;&#93;);
diff -dur src-Ch/value.h src-BbCh/value.h
--- src-Ch/value.h      2010-07-09 13&#58;03&#58;34.000000000 +0200
+++ src-BbCh/value.h    2010-07-20 22&#58;24&#58;14.000000000 +0200
@@ -36,7 +36,8 @@
   VALUE_TYPE_NONE  = 0,
   VALUE_TYPE_UPPER = 1,  // Upper bound
   VALUE_TYPE_LOWER = 2,  // Lower bound
-  VALUE_TYPE_EXACT = VALUE_TYPE_UPPER | VALUE_TYPE_LOWER
+  VALUE_TYPE_BOTH  = VALUE_TYPE_UPPER | VALUE_TYPE_LOWER,
+  VALUE_TYPE_EXACT = 7
 &#125;;


@@ -44,8 +45,8 @@
   VALUE_DRAW = 0,
   VALUE_KNOWN_WIN = 15000,
   VALUE_MATE = 30000,
-  VALUE_INFINITE = 30001,
-  VALUE_NONE = 30002,
+  VALUE_INFINITE = 30004, // because TT needs GrainSize>=4
+  VALUE_NONE = 30008,
   VALUE_ENSURE_SIGNED = -1
 &#125;;

Testing conditions:
tc=/0:40+.1 option.Threads=1 option.Hash=32 option.Ponder=false -pgnin gaviota-starters.pgn -concurrency 1 -repeat -games 1000
hash clear between games
make build ARCH=x86-64 COMP=gcc
around 680kps on 1 thread at startposition.
mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: Stockfish 1.8 tweaks

Post by mcostalba »

QED wrote: Idea is simple, make transposition table store both lower bound and upper bound with their respective depths.
We tested this Fruit idea long time ago (before to use TT also for evaluation score) and it was unsuccesfull even at that time.

I would suggest, before to start with a big and invasive patch to ask us about the idea, just to avoid an already tried effort.

I have sent to you a pm
QED
Posts: 60
Joined: Thu Nov 05, 2009 9:53 pm

Re: Stockfish 1.8 tweaks

Post by QED »

Richard Vida wrote:Mate in 15, bm h3

[D]8/8/8/2p5/1pp5/brpp4/1pprp2P/qnkbK3 w - - 0 1

Most engines reach their maximum depth with a draw score.
With this patch:

Code: Select all

diff -dur src-Ch/search.cpp src-Smrc2Ch/search.cpp
--- src-Ch/search.cpp   2010-07-09 13&#58;04&#58;18.000000000 +0200
+++ src-Smrc2Ch/search.cpp      2010-07-28 21&#58;13&#58;24.000000000 +0200
@@ -1058,7 +1058,7 @@
     const TTEntry* tte;
     Key posKey;
     Move ttMove, move, excludedMove;
-    Depth ext, newDepth;
+    Depth ext, newDepth, oldDepth = depth;
     Value bestValue, value, oldAlpha;
     Value refinedValue, nullValue, futilityValueScaled; // Non-PV specific
     bool isCheck, singleEvasion, singularExtensionNode, moveIsCheck, captureOrPromotion, dangerous;
@@ -1210,7 +1210,9 @@
                 return nullValue;

             ss->skipNullMove = true;
+            &#40;ss-1&#41;->reduction += 5*OnePly;
             Value v = search<NonPV>&#40;pos, ss, alpha, beta, depth-5*OnePly, ply&#41;;
+            &#40;ss-1&#41;->reduction -= 5*OnePly;
             ss->skipNullMove = false;

             if &#40;v >= beta&#41;
@@ -1285,8 +1287,7 @@
       // its siblings. To verify this we do a reduced search on all the other moves but the
       // ttMove, if result is lower then ttValue minus a margin then we extend ttMove.
       if (   singularExtensionNode
-          && move == tte->move&#40;)
-          && ext < OnePly&#41;
+          && move == tte->move&#40;))
       &#123;
           Value ttValue = value_from_tt&#40;tte->value&#40;), ply&#41;;

@@ -1295,12 +1296,17 @@
               Value b = ttValue - SingularExtensionMargin;
               ss->excludedMove = move;
               ss->skipNullMove = true;
-              Value v = search<NonPV>&#40;pos, ss, b - 1, b, depth / 2, ply&#41;;
+              assert&#40;&#40;depth + &#40;ss-1&#41;->reduction&#41; / 2 <= depth&#41;;
+              Value v = search<NonPV>&#40;pos, ss, b - 1, b, &#40;depth + &#40;ss-1&#41;->reduction&#41; / 2, ply&#41;;
               ss->skipNullMove = false;
               ss->excludedMove = MOVE_NONE;
               if &#40;v < ttValue - SingularExtensionMargin&#41;
                   ext = OnePly;
+              else
+                  singularExtensionNode = false;
           &#125;
+          else
+              singularExtensionNode = false;
       &#125;

       newDepth = depth - OnePly + ext;
@@ -1396,6 +1402,19 @@
           &#125;
       &#125;

+      // Singular Move Reduction Cancellation.
+      if (   singularExtensionNode
+          && ttMove == move
+          && &#40;ss-1&#41;->reduction
+          && value >= beta&#41;
+      &#123;
+          depth += &#40;ss-1&#41;->reduction;
+          newDepth += &#40;ss-1&#41;->reduction;
+          assert&#40;PvNode == NonPV&#41;;
+          value = newDepth < OnePly ? -qsearch<NonPV>&#40;pos, ss+1, -&#40;alpha+1&#41;, -alpha, Depth&#40;0&#41;, ply+1&#41;
+                                    &#58; - search<NonPV>&#40;pos, ss+1, -&#40;alpha+1&#41;, -alpha, newDepth, ply+1&#41;;
+      &#125;
+
       // Step 16. Undo move
       pos.undo_move&#40;move&#41;;

@@ -1444,7 +1463,7 @@

     ValueType f = &#40;bestValue <= oldAlpha ? VALUE_TYPE_UPPER &#58; bestValue >= beta ? VALUE_TYPE_LOWER &#58; VALUE_TYPE_EXACT&#41;;
     move = &#40;bestValue <= oldAlpha ? MOVE_NONE &#58; ss->bestMove&#41;;
-    TT.store&#40;posKey, value_to_tt&#40;bestValue, ply&#41;, f, depth, move, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);
+    TT.store&#40;posKey, value_to_tt&#40;bestValue, ply&#41;, f, oldDepth, move, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);

     // Update killers and history only for non capture moves that fails high
     if &#40;bestValue >= beta&#41;
Stockfish finds the mate at depth 29:

Code: Select all

Stockfish 1.8 by Tord Romstad, Marco Costalba, Joona Kiiski
setoption name Threads value 1
setoption name Hash value 1
position fen 8/8/8/2p5/1pp5/brpp4/1pprp2P/qnkbK3 w - - 0 1 
go depth 30
info depth 1
info depth 1 score cp -5898 time 138 nodes 6 nps 43 pv h2h4
info depth 2
info depth 2 score cp -5915 time 138 nodes 8 nps 57 pv h2h4 a1a2 
info depth 3
info depth 3 score cp -5830 time 139 nodes 17 nps 122 pv h2h4 a1a2 h4h5 a2a1 
info depth 4
info depth 4 score cp -5765 time 139 nodes 27 nps 194 pv h2h4 a1a2 h4h5 a2a1 h5h6 
info depth 5
info depth 5 score cp -5781 time 139 nodes 40 nps 287 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 
info depth 6
info depth 6 score cp -5632 lowerbound time 139 nodes 52 nps 374 pv h2h4 a1a2 
info depth 6 score cp -5632 lowerbound time 139 nodes 60 nps 431 pv h2h4 a1a2 
info depth 6 score cp -4307 lowerbound time 139 nodes 69 nps 496 pv h2h4 a1a2 
info depth 6 score cp -4307 lowerbound time 139 nodes 78 nps 561 pv h2h4 a1a2 
info depth 6 score cp -4307 lowerbound time 139 nodes 87 nps 625 pv h2h4 a1a2 
info depth 6 score cp -4307 time 139 nodes 99 nps 712 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q 
info depth 7
info depth 7 score cp 0 lowerbound time 139 nodes 141 nps 1014 pv h2h4 a1a2 
info depth 7 score cp -4311 time 139 nodes 162 nps 1165 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 
info depth 8
info depth 8 score cp 0 lowerbound time 140 nodes 191 nps 1364 pv h2h4 a1a2 
info depth 8 score cp 0 lowerbound time 140 nodes 205 nps 1464 pv h2h4 a1a2 
info depth 8 score cp 0 lowerbound time 140 nodes 219 nps 1564 pv h2h4 a1a2 
info depth 8 score cp 0 time 140 nodes 261 nps 1864 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 9
info depth 9 score cp 0 time 140 nodes 368 nps 2628 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 10
info depth 10 score cp 0 time 141 nodes 621 nps 4404 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 11
info depth 11 score cp 0 time 141 nodes 926 nps 6567 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 12
info depth 12 score cp 0 time 142 nodes 1242 nps 8746 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 13
info depth 13 score cp 0 time 143 nodes 1598 nps 11174 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 14
info depth 14 score cp 0 time 156 nodes 2132 nps 13666 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 15
info depth 15 score cp 0 time 158 nodes 3208 nps 20303 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 16
info depth 16 score cp 0 time 163 nodes 6271 nps 38472 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 17
info depth 17 score cp 0 time 180 nodes 12384 nps 68800 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 18
info depth 18 score cp 0 time 208 nodes 20947 nps 100706 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 19
info depth 19 score cp 0 time 229 nodes 30941 nps 135113 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 20
info depth 20 score cp 0 time 261 nodes 44883 nps 171965 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 21
info depth 21 score cp 0 time 309 nodes 64275 nps 208009 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 22
info depth 22 score cp 0 time 357 nodes 85681 nps 240002 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 23
info depth 23 score cp 0 time 406 nodes 113161 nps 278721 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 24
info depth 24 score cp 0 time 512 nodes 150062 nps 293089 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 25
info depth 25 score cp 0 time 604 nodes 181130 nps 299884 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 26
info depth 26 score cp 0 time 635 nodes 195618 nps 308059 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 27
info depth 27 score cp 0 time 714 nodes 226768 nps 317602 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 28
info depth 28 score cp 0 time 776 nodes 251436 nps 324015 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 29
info depth 29 score cp 0 time 896 nodes 286145 nps 319358 pv h2h4 a1a2 h4h5 a2a1 h5h6 a1a2 h6h7 a2a1 h7h8q a1a2 h8h1 a2a1 h1h8 
info depth 29 score mate 15 lowerbound time 939 nodes 300583 nps 320109 pv h2h3 a1a2 
info depth 29 score mate 15 lowerbound time 939 nodes 300616 nps 320144 pv h2h3 a1a2 
info depth 29 score mate 15 lowerbound time 939 nodes 300649 nps 320179 pv h2h3 a1a2 
info depth 29 score mate 15 lowerbound time 939 nodes 300682 nps 320215 pv h2h3 a1a2 
info depth 29 score mate 15 lowerbound time 940 nodes 300715 nps 319909 pv h2h3 a1a2 
info depth 29 score mate 15 lowerbound time 940 nodes 300748 nps 319944 pv h2h3 a1a2 
info depth 29 score mate 15 lowerbound time 940 nodes 300781 nps 319979 pv h2h3 a1a2 
info depth 29 score mate 15 lowerbound time 940 nodes 300814 nps 320014 pv h2h3 a1a2 
info depth 29 score mate 15 lowerbound time 940 nodes 300847 nps 320050 pv h2h3 a1a2 
info depth 29 score mate 15 lowerbound time 940 nodes 300880 nps 320085 pv h2h3 a1a2 
info depth 29 score mate 15 lowerbound time 940 nodes 300913 nps 320120 pv h2h3 a1a2 
info depth 29 score mate 15 time 1005 nodes 315415 nps 313845 pv h2h3 a1a2 h3h4 a2a1 h4h5 a1a2 h5h6 a2a1 h6h7 a1a2 h7h8n a2a1 h8g6 a1a2 g6f8 a2a1 f8e6 a1a2 e6c5 a2a1 c5d7 a1a2 d7b6 a2a1 b6c4 a1a2 c4a5 a2a1 
info currmove e1f2 currmovenumber 3
info depth 30
info currmove h2h3 currmovenumber 1
info nodes 328458 nps 312817 time 1049 hashfull 0
info depth 30 score mate 15 time 1137 nodes 344968 nps 303401 pv h2h3 a1a2 h3h4 a2a1 h4h5 a1a2 h5h6 a2a1 h6h7 a1a2 h7h8n a2a1 h8g6 a1a2 g6f8 a2a1 f8e6 a1a2 e6c5 a2a1 c5d7 a1a2 d7b6 a2a1 b6c4 a1a2 c4a5 a2a1 
info currmove h2h4 currmovenumber 2
info currmove e1f2 currmovenumber 3
info nodes 488294 nps 300303 time 1626 hashfull 0
bestmove h2h3 ponder a1a2
quit
I wanted to test it in real games, original stockfish lost +187 =608 -205 (LOS=70:29), but I had a bug there ("value > beta" instead of "value >= beta" in SMRC condition), so that test may be only noise from very similar-in-strength versions. Or only a good luck, to be expected after many negative tests. Or both.
Anyway, test with the correct version is under way.

The idea of Singular Move Reduction Cancellation (SMRC) is to enhance Singular Move Extension. Because Singular Move means there is a severe threat, but we try to reduce only such moves, that are not dangerous. So the node with SM should not be searched with reduced depth.

My implementation works like this: If we have found a (TT-)singular move, and it was a reduced depth node (from LMR in the parent node or null move zugzwang verification of the same node), and this move made a cut-off (else the caller will research it anyway), then silently set depth as if it was not a reduced node and research the singular move with this depth and continue with this depth even if the move now did not cut (good for the root move ordering), but store to transposotion table only the reduced depth, to avoid TT hits on future researches (to ensure continuous deepening with non-reduced internal depths).
Testing conditions:
tc=/0:40+.1 option.Threads=1 option.Hash=32 option.Ponder=false -pgnin gaviota-starters.pgn -concurrency 1 -repeat -games 1000
hash clear between games
make build ARCH=x86-64 COMP=gcc
around 680kps on 1 thread at startposition.
QED
Posts: 60
Joined: Thu Nov 05, 2009 9:53 pm

Re: Stockfish 1.8 tweaks

Post by QED »

mcostalba wrote:
QED wrote: Idea is simple, make transposition table store both lower bound and upper bound with their respective depths.
We tested this Fruit idea long time ago (before to use TT also for evaluation score) and it was unsuccesfull even at that time.
I have removed few minor bugs, but the thing is still (probably) weak, weaker than mere nps would suggest. Has anyone an idea why is it so? Is it really useful to forget previous bound (singular move testing, root fail high/fail low, ...) even when the bounds are compatible? Maybe there is some mechanism in search I do not see yet. This bothers me, because it makes me less productive.
Testing conditions:
tc=/0:40+.1 option.Threads=1 option.Hash=32 option.Ponder=false -pgnin gaviota-starters.pgn -concurrency 1 -repeat -games 1000
hash clear between games
make build ARCH=x86-64 COMP=gcc
around 680kps on 1 thread at startposition.
User avatar
michiguel
Posts: 6401
Joined: Thu Mar 09, 2006 8:30 pm
Location: Chicago, Illinois, USA

Re: Stockfish 1.8 tweaks

Post by michiguel »

mcostalba wrote:
QED wrote: Idea is simple, make transposition table store both lower bound and upper bound with their respective depths.
We tested this Fruit idea long time ago (before to use TT also for evaluation score) and it was unsuccesfull even at that time.

I would suggest, before to start with a big and invasive patch to ask us about the idea, just to avoid an already tried effort.

I have sent to you a pm
That is way older than Fruit. In fact, it is almost a necessity for MTD engines.

Miguel
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Stockfish 1.8 tweaks

Post by bob »

michiguel wrote:
mcostalba wrote:
QED wrote: Idea is simple, make transposition table store both lower bound and upper bound with their respective depths.
We tested this Fruit idea long time ago (before to use TT also for evaluation score) and it was unsuccesfull even at that time.

I would suggest, before to start with a big and invasive patch to ask us about the idea, just to avoid an already tried effort.

I have sent to you a pm
That is way older than Fruit. In fact, it is almost a necessity for MTD engines.

Miguel
I agree. This goes back to early 90's at least. I have an old mtd(f) version of Crafty I worked on for several months, and discovered that the double-bound TT is critical. But even after adding that, I never got mtd(f) to perform even as well as normal crafty, much less better. Not really sure why I even bothered saving that stuff since the basic internals have changed so much since then...
QED
Posts: 60
Joined: Thu Nov 05, 2009 9:53 pm

Re: Stockfish 1.8 tweaks

Post by QED »

Original stockfish lost to SMRC +186 =598 -216 (LOS=15:84), that looks promising. Next test will be SMRC version of adaptive Tinapa.
Testing conditions:
tc=/0:40+.1 option.Threads=1 option.Hash=32 option.Ponder=false -pgnin gaviota-starters.pgn -concurrency 1 -repeat -games 1000
hash clear between games
make build ARCH=x86-64 COMP=gcc
around 680kps on 1 thread at startposition.
mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: Stockfish 1.8 tweaks

Post by mcostalba »

QED wrote:Original stockfish lost to SMRC +186 =598 -216 (LOS=15:84), that looks promising. Next test will be SMRC version of adaptive Tinapa.
Hi Vratko, your idea is interesting, but I have to look a bit further because is not easy to understand for me.

I have sent you a pm.
QED
Posts: 60
Joined: Thu Nov 05, 2009 9:53 pm

Re: Stockfish 1.8 tweaks

Post by QED »

Vratko Polák wrote:Next test will be SMRC version of adaptive Tinapa.
Original lost only narrowly: +192 =611 -197 (LOS=42:57). That could mean anything (again).

And I forgot to add, that SMRC patch is 1 thread only yet.

For completeness, here is the tested patch:

Code: Select all

diff -dur src-Ch/search.cpp src-TiAvSmrcCh/search.cpp
--- src-Ch/search.cpp   2010-07-09 13&#58;04&#58;18.000000000 +0200
+++ src-TiAvSmrcCh/search.cpp   2010-07-30 20&#58;42&#58;32.000000000 +0200
@@ -1058,7 +1058,7 @@
     const TTEntry* tte;
     Key posKey;
     Move ttMove, move, excludedMove;
-    Depth ext, newDepth;
+    Depth ext, newDepth, oldDepth = depth;
     Value bestValue, value, oldAlpha;
     Value refinedValue, nullValue, futilityValueScaled; // Non-PV specific
     bool isCheck, singleEvasion, singularExtensionNode, moveIsCheck, captureOrPromotion, dangerous;
@@ -1185,7 +1185,7 @@
         ss->currentMove = MOVE_NULL;

         // Null move dynamic reduction based on depth
-        int R = 3 + &#40;depth >= 5 * OnePly ? depth / 8 &#58; 0&#41;;
+        int R = 3 + &#40;depth > 4*OnePly ? &#40;int&#40;depth&#41; - 3*int&#40;OnePly&#41;/2&#41; / &#40;3*int&#40;OnePly&#41;) &#58; 0&#41;; // inspired by Tinapa 1.01

         // Null move dynamic reduction based on value
         if &#40;refinedValue - beta > PawnValueMidgame&#41;
@@ -1206,11 +1206,13 @@
                 nullValue = beta;

             // Do zugzwang verification search at high depths
-            if &#40;depth < 6 * OnePly&#41;
+            if &#40;depth-R*OnePly < OnePly&#41;
                 return nullValue;

             ss->skipNullMove = true;
-            Value v = search<NonPV>&#40;pos, ss, alpha, beta, depth-5*OnePly, ply&#41;;
+            &#40;ss-1&#41;->reduction += R*OnePly;
+            Value v = search<NonPV>&#40;pos, ss, alpha, beta, depth-R*OnePly, ply&#41;;
+            &#40;ss-1&#41;->reduction -= R*OnePly;
             ss->skipNullMove = false;

             if &#40;v >= beta&#41;
@@ -1285,8 +1287,7 @@
       // its siblings. To verify this we do a reduced search on all the other moves but the
       // ttMove, if result is lower then ttValue minus a margin then we extend ttMove.
       if (   singularExtensionNode
-          && move == tte->move&#40;)
-          && ext < OnePly&#41;
+          && move == tte->move&#40;))
       &#123;
           Value ttValue = value_from_tt&#40;tte->value&#40;), ply&#41;;

@@ -1295,12 +1296,17 @@
               Value b = ttValue - SingularExtensionMargin;
               ss->excludedMove = move;
               ss->skipNullMove = true;
-              Value v = search<NonPV>&#40;pos, ss, b - 1, b, depth / 2, ply&#41;;
+              assert&#40;&#40;depth + &#40;ss-1&#41;->reduction&#41; / 2 <= depth&#41;;
+              Value v = search<NonPV>&#40;pos, ss, b - 1, b, &#40;depth + &#40;ss-1&#41;->reduction&#41; / 2, ply&#41;;
               ss->skipNullMove = false;
               ss->excludedMove = MOVE_NONE;
               if &#40;v < ttValue - SingularExtensionMargin&#41;
                   ext = OnePly;
+              else
+                  singularExtensionNode = false;
           &#125;
+          else
+              singularExtensionNode = false;
       &#125;

       newDepth = depth - OnePly + ext;
@@ -1396,6 +1402,19 @@
           &#125;
       &#125;

+      // Singular Move Reduction Cancellation.
+      if (   singularExtensionNode
+          && ttMove == move
+          && &#40;ss-1&#41;->reduction
+          && value >= beta&#41;
+      &#123;
+          depth += &#40;ss-1&#41;->reduction;
+          newDepth += &#40;ss-1&#41;->reduction;
+          assert&#40;PvNode == NonPV&#41;;
+          value = newDepth < OnePly ? -qsearch<NonPV>&#40;pos, ss+1, -&#40;alpha+1&#41;, -alpha, Depth&#40;0&#41;, ply+1&#41;
+                                    &#58; - search<NonPV>&#40;pos, ss+1, -&#40;alpha+1&#41;, -alpha, newDepth, ply+1&#41;;
+      &#125;
+
       // Step 16. Undo move
       pos.undo_move&#40;move&#41;;

@@ -1444,7 +1463,7 @@

     ValueType f = &#40;bestValue <= oldAlpha ? VALUE_TYPE_UPPER &#58; bestValue >= beta ? VALUE_TYPE_LOWER &#58; VALUE_TYPE_EXACT&#41;;
     move = &#40;bestValue <= oldAlpha ? MOVE_NONE &#58; ss->bestMove&#41;;
-    TT.store&#40;posKey, value_to_tt&#40;bestValue, ply&#41;, f, depth, move, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);
+    TT.store&#40;posKey, value_to_tt&#40;bestValue, ply&#41;, f, oldDepth, move, ss->eval, ei.kingDanger&#91;pos.side_to_move&#40;)&#93;);

     // Update killers and history only for non capture moves that fails high
     if &#40;bestValue >= beta&#41;
Testing conditions:
tc=/0:40+.1 option.Threads=1 option.Hash=32 option.Ponder=false -pgnin gaviota-starters.pgn -concurrency 1 -repeat -games 1000
hash clear between games
make build ARCH=x86-64 COMP=gcc
around 680kps on 1 thread at startposition.