From: drh <> Date: Tue, 1 Apr 2025 15:17:01 +0000 (+0000) Subject: This is an experimental optimization that attempts to keep a JSONB value X-Git-Tag: major-release~139^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3a7042e0e2257e50e3003ebac097665c118549e6;p=thirdparty%2Fsqlite.git This is an experimental optimization that attempts to keep a JSONB value the same size (same number of bytes) after doing a replace of an elements with a slightly smaller element, by denormalizing the size field. This can perhaps avoid unnecessary page updates and memmove() operations when making small changes in the middle of a large JSONB value. FossilOrigin-Name: b5de9584b7f49586c5387d8a74af5e41dba50f1817a54257bf9da00deb695f72 --- diff --git a/manifest b/manifest index 07a7e24e4d..be3b6eaf32 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\sharmless\stypo\sin\sa\scode\scomment. -D 2025-03-31T23:18:06.613 +C This\sis\san\sexperimental\soptimization\sthat\sattempts\sto\skeep\sa\sJSONB\svalue\nthe\ssame\ssize\s(same\snumber\sof\sbytes)\safter\sdoing\sa\sreplace\sof\san\selements\nwith\sa\sslightly\ssmaller\selement,\sby\sdenormalizing\sthe\ssize\sfield.\s\sThis\ncan\sperhaps\savoid\sunnecessary\spage\supdates\sand\smemmove()\soperations\swhen\nmaking\ssmall\schanges\sin\sthe\smiddle\sof\sa\slarge\sJSONB\svalue. +D 2025-04-01T15:17:01.606 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -746,7 +746,7 @@ F src/hash.h 46b92795a95bfefb210f52f0c316e9d7cdbcdd7e7fcfb0d8be796d3a5767cddf F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c a5f0366266be993ebf533808f22cb7a788624805b55bc45424ceed3f48c54a16 -F src/json.c 3c3975ba06163fde503200cf2827b3d49c862b1285ebe1e7fd2057dfada48ef9 +F src/json.c 1de02e754ea25c5c02ac1edd7b3b5fd20d0da55e74190d0942d779a2a00bf07a F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 F src/main.c 07f78d917ffcdf327982840cfd8e855fd000527a2ea5ace372ce4febcbd0bf97 @@ -2216,8 +2216,11 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 49a486c5069de041aedcbde4de178293e0463ae9918ecad7539eedf0ec77a139 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P e64c6a3856b839e4e8c0a1cb1713b0d2f1d3cb9b915dd215b0d3cb229502d539 -R d6afe3ea0b11e3d65d616d932eff7348 +P dd251377bd1a8e95a4a0179c50595f290b08ea93659f4906f88f5a9dff534aa1 +R d43087fc8a1c599b5542c89f392aa309 +T *branch * json-opt +T *sym-json-opt * +T -sym-trunk * U drh -Z e51963c174f092cb0aa94669a8f5d2cf +Z 9a10eb32279f050fc9d357fd9e297aac # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 26647656ca..6c4fe5533f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -dd251377bd1a8e95a4a0179c50595f290b08ea93659f4906f88f5a9dff534aa1 +b5de9584b7f49586c5387d8a74af5e41dba50f1817a54257bf9da00deb695f72 diff --git a/src/json.c b/src/json.c index 2108c3bc0e..18d8717789 100644 --- a/src/json.c +++ b/src/json.c @@ -23,8 +23,8 @@ ** Beginning with version 3.45.0 (circa 2024-01-01), these routines also ** accept BLOB values that have JSON encoded using a binary representation ** called "JSONB". The name JSONB comes from PostgreSQL, however the on-disk -** format SQLite JSONB is completely different and incompatible with -** PostgreSQL JSONB. +** format for SQLite-JSONB is completely different and incompatible with +** PostgreSQL-JSONB. ** ** Decoding and interpreting JSONB is still O(N) where N is the size of ** the input, the same as text JSON. However, the constant of proportionality @@ -81,7 +81,7 @@ ** ** The payload size need not be expressed in its minimal form. For example, ** if the payload size is 10, the size can be expressed in any of 5 different -** ways: (1) (X>>4)==10, (2) (X>>4)==12 following by on 0x0a byte, +** ways: (1) (X>>4)==10, (2) (X>>4)==12 following by one 0x0a byte, ** (3) (X>>4)==13 followed by 0x00 and 0x0a, (4) (X>>4)==14 followed by ** 0x00 0x00 0x00 0x0a, or (5) (X>>4)==15 followed by 7 bytes of 0x00 and ** a single byte of 0x0a. The shorter forms are preferred, of course, but @@ -91,7 +91,7 @@ ** the size when it becomes known, resulting in a non-minimal encoding. ** ** The value (X>>4)==15 is not actually used in the current implementation -** (as SQLite is currently unable handle BLOBs larger than about 2GB) +** (as SQLite is currently unable to handle BLOBs larger than about 2GB) ** but is included in the design to allow for future enhancements. ** ** The payload follows the header. NULL, TRUE, and FALSE have no payload and @@ -1165,7 +1165,7 @@ static SQLITE_NOINLINE void jsonBlobExpandAndAppendNode( } -/* Append an node type byte together with the payload size and +/* Append a node type byte together with the payload size and ** possibly also the payload. ** ** If aPayload is not NULL, then it is a pointer to the payload which @@ -2500,6 +2500,82 @@ static void jsonAfterEditSizeAdjust(JsonParse *pParse, u32 iRoot){ pParse->delta += jsonBlobChangePayloadSize(pParse, iRoot, sz); } +/* +** If the JSONB at aIns[0..nIns-1] can be expanded (by denormalizing the +** size field) by d bytes, then write the expansion into aOut[] and +** return true. In this way, an overwrite happens without changing the +** size of the JSONB, which reduces memcpy() operations and also make it +** faster and easier to update the B-Tree entry that contains the JSONB +** in the database. +** +** If the expansion of aIns[] by d bytes cannot be (easily) accomplished +** then return false. +** +** The d parameter is guaranteed to be between 1 and 8. +** +** This routine is an optimization. A correct answer is obtained if it +** always leaves the output unchanged and returns false. +*/ +static int jsonBlobOverwrite( + u8 *aOut, /* Overwrite here */ + const u8 *aIns, /* New content */ + u32 nIns, /* Bytes of new content */ + u32 d /* Need to expand new content by this much */ +){ + u32 szPayload; /* Bytes of payload */ + u32 i; /* New header size, after expansion & a loop counter */ + u8 szHdr; /* Size of header before expansion */ + + /* Lookup table for finding the upper 4 bits of the first byte of the + ** expanded aIns[], based on the size of the expanded aIns[] header: + ** + ** 2 3 4 5 6 7 8 9 */ + static const u8 aType[] = { 0xc0, 0xd0, 0, 0xe0, 0, 0, 0, 0xf0 }; + + if( (aIns[0]&0x0f)<=2 ) return ; /* Cannot enlarge NULL, true, false */ + switch( aIns[0]>>4 ){ + default: { /* aIns[] header size 1 */ + if( ((1<=2 && i<=9 && aType[i-2]!=0 ); + aOut[0] = (aIns[0] & 0x0f) | aType[i-2]; + memcpy(&aOut[i], &aIns[szHdr], nIns-szHdr); + szPayload = nIns - szHdr; + while( 1/*edit-by-break*/ ){ + i--; + aOut[i] = szPayload & 0xff; + if( i==1 ) break; + szPayload >>= 8; + } + assert( (szPayload>>8)==0 ); + return 1; +} + /* ** Modify the JSONB blob at pParse->aBlob by removing nDel bytes of ** content beginning at iDel, and replacing them with nIns bytes of @@ -2521,6 +2597,11 @@ static void jsonBlobEdit( u32 nIns /* Bytes of content to insert */ ){ i64 d = (i64)nIns - (i64)nDel; + if( d<0 && d>=(-8) && aIns!=0 + && jsonBlobOverwrite(&pParse->aBlob[iDel], aIns, nIns, (int)-d) + ){ + return; + } if( d!=0 ){ if( pParse->nBlob + d > pParse->nBlobAlloc ){ jsonBlobExpand(pParse, pParse->nBlob+d); @@ -2532,7 +2613,9 @@ static void jsonBlobEdit( pParse->nBlob += d; pParse->delta += d; } - if( nIns && aIns ) memcpy(&pParse->aBlob[iDel], aIns, nIns); + if( nIns && aIns ){ + memcpy(&pParse->aBlob[iDel], aIns, nIns); + } } /*