]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
An attempt to improve the performance of the IN-early-out optimization
authordrh <drh@noemail.net>
Mon, 31 Aug 2020 16:31:00 +0000 (16:31 +0000)
committerdrh <drh@noemail.net>
Mon, 31 Aug 2020 16:31:00 +0000 (16:31 +0000)
(see check-in [09fffbdf9f2f6ce3]) by avoiding unnecessary calls to the
b-tree search algorithm in OP_IfNoHope when the index key is at hand and
the same answer can be obtained by doing a quick key comparison.

FossilOrigin-Name: e9d983c6830efdfdb734409465f071f38acf3050571aadb9b05b885328baf77c

manifest
manifest.uuid
src/vdbe.c
src/vdbeInt.h
src/wherecode.c

index 3960de1cb17b45c90e08712a0486f89c0550b34a..c37268932b1270928115edb24fc5d8253c415f6a 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\sthe\sdocumentation\sfor\sthe\sOP_IdxGT\sfamily\sof\sopcodes\sto\sshow\sthat\sthe\nP5\soperand\sis\snot\sused.
-D 2020-08-31T12:29:03.480
+C An\sattempt\sto\simprove\sthe\sperformance\sof\sthe\sIN-early-out\soptimization\n(see\scheck-in\s[09fffbdf9f2f6ce3])\sby\savoiding\sunnecessary\scalls\sto\sthe\nb-tree\ssearch\salgorithm\sin\sOP_IfNoHope\swhen\sthe\sindex\skey\sis\sat\shand\sand\nthe\ssame\sanswer\scan\sbe\sobtained\sby\sdoing\sa\squick\skey\scomparison.
+D 2020-08-31T16:31:00.075
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -607,9 +607,9 @@ F src/upsert.c 2920de71b20f04fe25eb00b655d086f0ba60ea133c59d7fa3325c49838818e78
 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
 F src/util.c c0c7977de7ef9b8cb10f6c85f2d0557889a658f817b0455909a49179ba4c8002
 F src/vacuum.c 492422c1463c076473bae1858799c7a0a5fe87a133d1223239447c422cd26286
-F src/vdbe.c c5da1456c9de0993055be9c10ebc5f5eb2be75d28cb01c8abc2f083923835a2d
+F src/vdbe.c a0ce31a5dc3dd5a6933fb616e1d1fcafb2a09143580b729b8346777227e5231a
 F src/vdbe.h 83603854bfa5851af601fc0947671eb260f4363e62e960e8a994fb9bbcd2aaa1
-F src/vdbeInt.h 762abffb7709f19c2cb74af1bba73a900f762e64f80d69c31c9ae89ed1066b60
+F src/vdbeInt.h 43341faf09fb620acc962be62ae339e7b77715207862e2b2e596f7f2f39e3828
 F src/vdbeapi.c c5e7cb2ab89a24d7f723e87b508f21bfb1359a04db5277d8a99fd1e015c12eb9
 F src/vdbeaux.c b39d2e0e7126cd4629874dd7b67162b9f0d200b620d2b4c16d400949a2f1094b
 F src/vdbeblob.c 253ed82894924c362a7fa3079551d3554cd1cdace39aa833da77d3bc67e7c1b1
@@ -624,7 +624,7 @@ F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a
 F src/walker.c 3df26a33dc4f54e8771600fb7fdebe1ece0896c2ad68c30ab40b017aa4395049
 F src/where.c 23f47e845e304a41d0b221bf67bd170014ae08b673076813fcd945dda1a3d4af
 F src/whereInt.h eb8c2847fb464728533777efec1682b3c074224293b2da73513c61a609efbeab
-F src/wherecode.c 110fa357bf453e0d30bf5ebb2cc86ea34a3631c39b857f30c228fd325cb53ae7
+F src/wherecode.c 28b243fe624a0f111c7680eb0fe6e9c23b6b0a5b35f9a0f75e87ae1534b7615a
 F src/whereexpr.c 264d58971eaf8256eb5b0917bcd7fc7a1f1109fdda183a8382308a1b18a2dce7
 F src/window.c edd6f5e25a1e8f2b6f5305b7f5f7da7bb35f07f0d432b255b1d4c2fcab4205aa
 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
@@ -1879,7 +1879,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P ded1a75b3cf39834d38a385f38ae969b296f6c9409856b7eea08645e861b1ac2
-R d9747280ab6c456500686b04f563aa50
+P 62f7d2a61259f296ffdcb3b3ee1a13925c4563ac8ed669f8a8a63fc7bc3a0a37
+R fe444095b3e78d9325a2970b51b1a43d
+T *branch * in-early-out
+T *sym-in-early-out *
+T -sym-trunk *
 U drh
-Z 96026921b32a7e290f8ba5e4e39f8431
+Z effb995a21bb3b32c1f5d827a8cd2f01
index 417012c05cde5572276f341f4fd8057a6c3e3564..1be0ec858d3bacb7e3fb4d34c21a3f47b3c5714f 100644 (file)
@@ -1 +1 @@
-62f7d2a61259f296ffdcb3b3ee1a13925c4563ac8ed669f8a8a63fc7bc3a0a37
\ No newline at end of file
+e9d983c6830efdfdb734409465f071f38acf3050571aadb9b05b885328baf77c
\ No newline at end of file
index bc5addabab7f00e07025af7177b112c95a5a6520..63033986cf15af79f7423ba3110bddbdb4f5dd28 100644 (file)
@@ -137,6 +137,54 @@ static void test_trace_breakpoint(int pc, Op *pOp, Vdbe *v){
 }
 #endif
 
+
+#ifdef SQLITE_DEBUG
+/* This is a validation routine that occurs only inside of assert()
+** statements.  Its purpose is to verify that the generated bytecode
+** is correct.  It only runs on debugging builds.
+**
+** Verify that the pOp opcode is an OP_SeekGE or OP_SeekLE that is
+** followed by a corresponding OP_IdxGT or OP_IdxLT, possibly with
+** an intervening OP_SeekHit.  Return 1 if the constraint is true.
+** Return 0 if something unexpected is seen.
+*/
+static int seekFollowedByPairedIdxCompare(VdbeOp *pOp){
+  VdbeOp *pNext;
+
+  /* Find the subsequent opcode.  We are allowed to skip over a single
+  ** OP_SeekHit against the same cursor with a P2 value of 1.
+  */
+  pNext = pOp + 1;
+  if( pNext->opcode==OP_SeekHit ){
+    if( pNext->p2!=1 ) return 0;
+    if( pNext->p1!=pOp->p1 ) return 0;
+    pNext++;
+  }
+
+  /* This routine verifies that the opcodes are correct for a
+  ** BTREE_SEEK_EQ lookup.  So the two opcodes involved must be either:
+  **
+  **  *   OP_SeekGE followed by OP_IdxGT
+  **  *   OP_SeekLE followed by OP_IdxLT
+  */
+  if( pOp->opcode==OP_SeekGE ){
+    if( pNext->opcode!=OP_IdxGT ) return 0;
+  }else if( pOp->opcode==OP_SeekLE ){
+    if( pNext->opcode!=OP_IdxLT ) return 0;
+  }else{
+    return 0;
+  }
+
+  /* The OP_SeekXX and OP_IdxXX must have the same arguments.
+  */
+  if( pOp->p1!=pNext->p1 ) return 0;
+  if( pOp->p2!=pNext->p2 ) return 0;
+  if( pOp->p3!=pNext->p3 ) return 0;
+  if( pOp->p4.i!=pNext->p4.i ) return 0;
+  return 1;
+}
+#endif /* SQLITE_DEBUG */
+
 /*
 ** Invoke the VDBE coverage callback, if that callback is defined.  This
 ** feature is used for test suite validation only and does not appear an
@@ -4286,18 +4334,12 @@ case OP_SeekGT: {       /* jump, in3, group */
     /* For a cursor with the OPFLAG_SEEKEQ/BTREE_SEEK_EQ hint, only the
     ** OP_SeekGE and OP_SeekLE opcodes are allowed, and these must be
     ** immediately followed by an OP_IdxGT or OP_IdxLT opcode, respectively,
-    ** with the same key.
+    ** with the same key.  There might be a single OP_SeekHit opcode in
+    ** between the OP_SeekXX and OP_IdxXX.
     */
     if( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ) ){
-      eqOnly = 1;
-      assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE );
-      assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT );
-      assert( pOp->opcode==OP_SeekGE || pOp[1].opcode==OP_IdxLT );
-      assert( pOp->opcode==OP_SeekLE || pOp[1].opcode==OP_IdxGT );
-      assert( pOp[1].p1==pOp[0].p1 );
-      assert( pOp[1].p2==pOp[0].p2 );
-      assert( pOp[1].p3==pOp[0].p3 );
-      assert( pOp[1].p4.i==pOp[0].p4.i );
+      eqOnly = 1 + (pOp[1].opcode==OP_SeekHit);
+      assert( seekFollowedByPairedIdxCompare(pOp) );
     }
 
     nField = pOp->p4.i;
@@ -4377,8 +4419,8 @@ seek_not_found:
   if( res ){
     goto jump_to_p2;
   }else if( eqOnly ){
-    assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT );
-    pOp++; /* Skip the OP_IdxLt or OP_IdxGT that follows */
+    assert( pOp[eqOnly].opcode==OP_IdxLT || pOp[eqOnly].opcode==OP_IdxGT );
+    pOp += eqOnly; /* Skip the OP_IdxLt or OP_IdxGT that follows */
   }
   break;
 }
@@ -4389,16 +4431,16 @@ seek_not_found:
 ** Set the seekHit flag on cursor P1 to the value in P2.
 ** The seekHit flag is used by the IfNoHope opcode.
 **
-** P1 must be a valid b-tree cursor.  P2 must be a boolean value,
-** either 0 or 1.
+** P1 must be a valid b-tree cursor.  P2 must be an integer
+** between 0 and 3.
 */
 case OP_SeekHit: {
   VdbeCursor *pC;
   assert( pOp->p1>=0 && pOp->p1<p->nCursor );
   pC = p->apCsr[pOp->p1];
   assert( pC!=0 );
-  assert( pOp->p2==0 || pOp->p2==1 );
-  pC->seekHit = pOp->p2 & 1;
+  assert( pOp->p2>=0 && pOp->p2<=3 );
+  pC->seekHit = pOp->p2 & 3;
   break;
 }
 
@@ -4452,33 +4494,6 @@ case OP_IfNotOpen: {        /* jump */
 **
 ** See also: Found, NotExists, NoConflict, IfNoHope
 */
-/* Opcode: IfNoHope P1 P2 P3 P4 *
-** Synopsis: key=r[P3@P4]
-**
-** Register P3 is the first of P4 registers that form an unpacked
-** record.
-**
-** Cursor P1 is on an index btree.  If the seekHit flag is set on P1, then
-** this opcode is a no-op.  But if the seekHit flag of P1 is clear, then
-** check to see if there is any entry in P1 that matches the
-** prefix identified by P3 and P4.  If no entry matches the prefix,
-** jump to P2.  Otherwise fall through.
-**
-** This opcode behaves like OP_NotFound if the seekHit
-** flag is clear and it behaves like OP_Noop if the seekHit flag is set.
-**
-** This opcode is used in IN clause processing for a multi-column key.
-** If an IN clause is attached to an element of the key other than the
-** left-most element, and if there are no matches on the most recent
-** seek over the whole key, then it might be that one of the key element
-** to the left is prohibiting a match, and hence there is "no hope" of
-** any match regardless of how many IN clause elements are checked.
-** In such a case, we abandon the IN clause search early, using this
-** opcode.  The opcode name comes from the fact that the
-** jump is taken if there is "no hope" of achieving a match.
-**
-** See also: NotFound, SeekHit
-*/
 /* Opcode: NoConflict P1 P2 P3 P4 *
 ** Synopsis: key=r[P3@P4]
 **
@@ -4502,15 +4517,6 @@ case OP_IfNotOpen: {        /* jump */
 **
 ** See also: NotFound, Found, NotExists
 */
-case OP_IfNoHope: {     /* jump, in3 */
-  VdbeCursor *pC;
-  assert( pOp->p1>=0 && pOp->p1<p->nCursor );
-  pC = p->apCsr[pOp->p1];
-  assert( pC!=0 );
-  if( pC->seekHit ) break;
-  /* Fall through into OP_NotFound */
-  /* no break */ deliberate_fall_through
-}
 case OP_NoConflict:     /* jump, in3 */
 case OP_NotFound:       /* jump, in3 */
 case OP_Found: {        /* jump, in3 */
@@ -4527,6 +4533,7 @@ case OP_Found: {        /* jump, in3 */
   if( pOp->opcode!=OP_NoConflict ) sqlite3_found_count++;
 #endif
 
+vdbe_op_notfound:    /* OP_IfNoHope jumps here, sometimes */
   assert( pOp->p1>=0 && pOp->p1<p->nCursor );
   assert( pOp->p4type==P4_INT32 );
   pC = p->apCsr[pOp->p1];
@@ -5865,14 +5872,15 @@ case OP_FinishSeek: {
 ** If the P1 index entry is less than or equal to the key value then jump
 ** to P2. Otherwise fall through to the next instruction.
 */
-case OP_IdxLE:          /* jump */
-case OP_IdxGT:          /* jump */
-case OP_IdxLT:          /* jump */
-case OP_IdxGE:  {       /* jump */
+case OP_IdxLE:          /* jump, group */
+case OP_IdxGT:          /* jump, group */
+case OP_IdxLT:          /* jump, group */
+case OP_IdxGE:  {       /* jump, group */
   VdbeCursor *pC;
   int res;
   UnpackedRecord r;
 
+vdbe_op_idxne:          /* Virtual OP_IdxNE opcode.  See OP_IfNoHope */
   assert( pOp->p1>=0 && pOp->p1<p->nCursor );
   pC = p->apCsr[pOp->p1];
   assert( pC!=0 );
@@ -5887,7 +5895,8 @@ case OP_IdxGE:  {       /* jump */
     assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxGT );
     r.default_rc = -1;
   }else{
-    assert( pOp->opcode==OP_IdxGE || pOp->opcode==OP_IdxLT );
+    assert( pOp->opcode==OP_IdxGE || pOp->opcode==OP_IdxLT
+           || pOp->opcode==OP_IfNoHope );
     r.default_rc = 0;
   }
   r.aMem = &aMem[pOp->p3];
@@ -5904,7 +5913,8 @@ case OP_IdxGE:  {       /* jump */
   rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res);
   assert( (OP_IdxLE&1)==(OP_IdxLT&1) && (OP_IdxGE&1)==(OP_IdxGT&1) );
   if( (pOp->opcode&1)==(OP_IdxLT&1) ){
-    assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxLT );
+    assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxLT
+             || pOp->opcode==OP_IfNoHope );
     res = -res;
   }else{
     assert( pOp->opcode==OP_IdxGE || pOp->opcode==OP_IdxGT );
@@ -5916,6 +5926,71 @@ case OP_IdxGE:  {       /* jump */
   break;
 }
 
+/* Opcode: IfNoHope P1 P2 P3 P4 *
+** Synopsis: key=r[P3@P4]
+**
+** Register P3 is the first of P4 registers that form an unpacked
+** record.  P1 is number of an index-btree cursor.  P2 is a jump
+** destination.  So, in other words, the operands to this opcode
+** are the same as the operands to OP_NotFound and OP_IdxGT.
+**
+** The behavior of this opcode depends on the current seekFlag setting.
+** (See the OP_SeekHit.)
+**
+** <ul>
+** <li> seekHit==0 &rarr;  OP_NotFound
+** <li> seekHit==1 &rarr;  OP_IdxGT or OP_IdxLT
+** <li> seekHit==2 &rarr;  OP_Noop
+** </ul>
+**
+** Actually, when seekHit==1, this routine behaves as if it were an
+** OP_IdxNE opcode.  It has all the same parameters as OP_IdxGT and 
+** OP_IdxLT, but the jump is taken if the key is not equal to the index
+** record, rather than if the key is greater than or less than the
+** index record, respectively.
+**
+** This opcode is used in IN clause processing for a multi-column key.
+** If an IN clause is attached to an element of the key other than the
+** left-most element, and if there are no matches on the most recent
+** seek over the whole key, then it might be that one of the key element
+** to the left is prohibiting a match, and hence there is "no hope" of
+** any match regardless of how many IN clause elements are checked.
+** In such a case, we abandon the IN clause search early, using this
+** opcode.  The opcode name comes from the fact that the
+** jump is taken if there is "no hope" of achieving a match.
+**
+** This opcode is an optimization.  It can always behave as OP_Noop
+** and the correct result will still be obtained, though perhaps not
+** quite as quickly.
+**
+** See also: NotFound, SeekHit, IdxGT
+*/
+case OP_IfNoHope: {     /* jump, in3, group */
+  VdbeCursor *pC;
+  assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+  pC = p->apCsr[pOp->p1];
+  assert( pC!=0 );
+  if( pC->seekHit>=2 ){
+    /* There has been one or more successful OP_IdxXX opcodes ("successful"
+    ** in the sense that the jump was not taken because the key and index
+    ** matched).  So a match is possible.  We do not want to abort.  Fall
+    ** through to the next opcode without doing anything. */
+    break;
+  }else if( pC->seekHit==1 ){
+    /* The initial OP_SeekXX opcode was successful (in that it found a
+    ** candidate row) but all subsequent OP_IdxXX opcodes failed.  The
+    ** index should have been left pointing at the candidate row.  In this
+    ** case, this opcode works like a "OP_IdxNE" opcode - similar to
+    ** OP_IdxGT but instead of jumping if the index entry is greater than
+    ** the key, it jumps if the index entry is not equal to the key.
+    */
+    goto vdbe_op_idxne;
+  }
+  /* The initial OP_SeekXX opcode took its jump, meaning that it found
+  ** no candidate rows.  Treat this opcode as if it were an OP_NotFound */
+  goto vdbe_op_notfound;
+}
+
 /* Opcode: Destroy P1 P2 P3 * *
 **
 ** Delete an entire database table or index whose root page in the database
index 901569742f5b1ff8f9f28855976458576b49d416..3c0dccb0cf9f5d5944d469283b74af75ea3949a5 100644 (file)
@@ -86,7 +86,7 @@ struct VdbeCursor {
   Bool isEphemeral:1;     /* True for an ephemeral table */
   Bool useRandomRowid:1;  /* Generate new record numbers semi-randomly */
   Bool isOrdered:1;       /* True if the table is not BTREE_UNORDERED */
-  Bool seekHit:1;         /* See the OP_SeekHit and OP_IfNoHope opcodes */
+  unsigned seekHit:2;     /* See the OP_SeekHit and OP_IfNoHope opcodes */
   Btree *pBtx;            /* Separate file holding temporary table */
   i64 seqCount;           /* Sequence counter */
   u32 *aAltMap;           /* Mapping from table to index column numbers */
index 145b6fa5f52e5a76ffcb9429b53d32369a72d1a3..f17cda94e63b91aa87e547c49c11e0bd0e1f208a 100644 (file)
@@ -1870,6 +1870,9 @@ Bitmask sqlite3WhereCodeOneLoopStart(
 
     /* Check if the index cursor is past the end of the range. */
     if( nConstraint ){
+      if( pLoop->wsFlags & WHERE_IN_EARLYOUT ){
+        sqlite3VdbeAddOp2(v, OP_SeekHit, iIdxCur, 1);
+      }
       if( regBignull ){
         /* Except, skip the end-of-range check while doing the NULL-scan */
         sqlite3VdbeAddOp2(v, OP_IfNot, regBignull, sqlite3VdbeCurrentAddr(v)+3);
@@ -1902,7 +1905,7 @@ Bitmask sqlite3WhereCodeOneLoopStart(
     }
 
     if( pLoop->wsFlags & WHERE_IN_EARLYOUT ){
-      sqlite3VdbeAddOp2(v, OP_SeekHit, iIdxCur, 1);
+      sqlite3VdbeAddOp2(v, OP_SeekHit, iIdxCur, 2);
     }
 
     /* Seek the table cursor, if required */