]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Experimental change: If SQLITE_PAGECACHE_BLOCKALLOC is defined, instead of allocating...
authordan <dan@noemail.net>
Fri, 19 Aug 2011 18:15:00 +0000 (18:15 +0000)
committerdan <dan@noemail.net>
Fri, 19 Aug 2011 18:15:00 +0000 (18:15 +0000)
FossilOrigin-Name: 63597097eedf80080fab0c1978cfd66ecaaa79fa

manifest
manifest.uuid
src/pcache1.c

index f897b97073cd64b0098abcd5567ca392b80c8590..54adbea9e69613079ee29646a1181dfb37366065 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C When\sretrying\sa\swrite()\safter\san\sEINTR\serror\son\sunix,\sbe\ssure\sto\salso\nrerun\sthe\sprevious\slseek().\s\sTicket\s[e59bdf6116036a]
-D 2011-08-19T14:54:12.709
+C Experimental\schange:\sIf\sSQLITE_PAGECACHE_BLOCKALLOC\sis\sdefined,\sinstead\sof\sallocating\spages\sone\sat\sa\stime,\sallocate\sblocks\sof\sbetween\s15\sand\s63\spages\sin\sa\ssingle\sallocation.
+D 2011-08-19T18:15:00.523
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 8c930e7b493d59099ea1304bd0f2aed152eb3315
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -172,7 +172,7 @@ F src/pager.h 3f8c783de1d4706b40b1ac15b64f5f896bcc78d1
 F src/parse.y 12b7ebd61ea54f0e1b1083ff69cc2c8ce9353d58
 F src/pcache.c 49e718c095810c6b3334e3a6d89970aceaddefce
 F src/pcache.h c683390d50f856d4cd8e24342ae62027d1bb6050
-F src/pcache1.c 912bd5687d6df344698d8e69560f347b6e21c18a
+F src/pcache1.c 0a131cf9999ba5baeb82fe1b073924ef115af853
 F src/pragma.c ebcd20f1e654f5cb3aeef864ed69c4697719fbaa
 F src/prepare.c e64261559a3187698a3e7e6c8b001a4f4f98dab4
 F src/printf.c 585a36b6a963df832cfb69505afa3a34ed5ef8a1
@@ -960,7 +960,10 @@ F tool/symbols.sh caaf6ccc7300fd43353318b44524853e222557d5
 F tool/tostr.awk 11760e1b94a5d3dcd42378f3cc18544c06cfa576
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
 F tool/warnings.sh 2ebae31e1eb352696f3c2f7706a34c084b28c262
-P 928bcaf0f00a408e2f6c1d85dfab214457f52ad5
-R 843a68fd8264832950d511247e1afac6
-U drh
-Z 08ca15b3cb3d5aca6ddea45758dd9762
+P 21452f3ae6b5882b03c7cc41e661c7b8144cc3df
+R 2997be16a993dfce2f800d3578e57dd4
+T *branch * pager-blockalloc
+T *sym-pager-blockalloc *
+T -sym-trunk *
+U dan
+Z 1efe07fa4b86c6fc00527f5dc4f02f5a
index 0672ce04fd64746c15c8c7e417a3d5cdcae3a30c..6588cb9e67131223e94b69fed13ef43931fca255 100644 (file)
@@ -1 +1 @@
-21452f3ae6b5882b03c7cc41e661c7b8144cc3df
\ No newline at end of file
+63597097eedf80080fab0c1978cfd66ecaaa79fa
\ No newline at end of file
index e47265a225a9cd7b6563ebf3ff35b62f39224c40..b39e453ddd4bbfd2b56303cb831fde1ba470c51c 100644 (file)
@@ -24,6 +24,9 @@ typedef struct PgHdr1 PgHdr1;
 typedef struct PgFreeslot PgFreeslot;
 typedef struct PGroup PGroup;
 
+typedef struct PGroupBlock PGroupBlock;
+typedef struct PGroupBlockList PGroupBlockList;
+
 /* Each page cache (or PCache) belongs to a PGroup.  A PGroup is a set 
 ** of one or more PCaches that are able to recycle each others unpinned
 ** pages when they are under memory pressure.  A PGroup is an instance of
@@ -53,8 +56,63 @@ struct PGroup {
   int mxPinned;                  /* nMaxpage + 10 - nMinPage */
   int nCurrentPage;              /* Number of purgeable pages allocated */
   PgHdr1 *pLruHead, *pLruTail;   /* LRU list of unpinned pages */
+  PGroupBlockList *pBlockList;   /* List of block-lists for this group */
+};
+
+/*
+** If SQLITE_PAGECACHE_BLOCKALLOC is defined when the library is built,
+** each PGroup structure has a linked list of the the following starting
+** at PGroup.pBlockList. There is one entry for each distinct page-size 
+** currently used by members of the PGroup (i.e. 1024 bytes, 4096 bytes
+** etc.). Variable PGroupBlockList.nByte is set to the actual allocation
+** size requested by each pcache, which is the database page-size plus
+** the various header structures used by the pcache, pager and btree layers.
+** Usually around (pgsz+200) bytes.
+**
+** This size (pgsz+200) bytes is not allocated efficiently by some
+** implementations of malloc. In particular, some implementations are only
+** able to allocate blocks of memory chunks of 2^N bytes, where N is some
+** integer value. Since the page-size is a power of 2, this means we
+** end up wasting (pgsz-200) bytes in each allocation.
+**
+** If SQLITE_PAGECACHE_BLOCKALLOC is defined, the (pgsz+200) byte blocks
+** are not allocated directly. Instead, blocks of roughly M*(pgsz+200) bytes 
+** are requested from malloc allocator. After a block is returned,
+** sqlite3MallocSize() is used to determine how many (pgsz+200) byte
+** allocations can fit in the space returned by malloc(). This value may
+** be more than M.
+**
+** The blocks are stored in a doubly-linked list. Variable PGroupBlock.nEntry
+** contains the number of allocations that will fit in the aData[] space.
+** nEntry is limited to the number of bits in bitmask mUsed. If a slot
+** within aData is in use, the corresponding bit in mUsed is set. Thus
+** when (mUsed+1==(1 << nEntry)) the block is completely full.
+**
+** Each time a slot within a block is freed, the block is moved to the start
+** of the linked-list. And if a block becomes completely full, then it is
+** moved to the end of the list. As a result, when searching for a free
+** slot, only the first block in the list need be examined. If it is full,
+** then it is guaranteed that all blocks are full.
+*/
+struct PGroupBlockList {
+  int nByte;                     /* Size of each allocation in bytes */
+  PGroupBlock *pFirst;           /* First PGroupBlock in list */
+  PGroupBlock *pLast;            /* Last PGroupBlock in list */
+  PGroupBlockList *pNext;        /* Next block-list attached to group */
 };
 
+struct PGroupBlock {
+  Bitmask mUsed;                 /* Mask of used slots */
+  int nEntry;                    /* Maximum number of allocations in aData[] */
+  u8 *aData;                     /* Pointer to data block */
+  PGroupBlock *pNext;            /* Next PGroupBlock in list */
+  PGroupBlock *pPrev;            /* Previous PGroupBlock in list */
+  PGroupBlockList *pList;        /* Owner list */
+};
+
+/* Minimum value for PGroupBlock.nEntry */
+#define PAGECACHE_BLOCKALLOC_MINENTRY 15
+
 /* Each page cache is an instance of the following object.  Every
 ** open database file (including each in-memory database and each
 ** temporary or transient database) has a single page cache which
@@ -158,6 +216,17 @@ static SQLITE_WSD struct PCacheGlobal {
 #define PGHDR1_TO_PAGE(p)    (void*)(((char*)p) - p->pCache->szPage)
 #define PAGE_TO_PGHDR1(c, p) (PgHdr1*)(((char*)p) + c->szPage)
 
+/*
+** Blocks used by the SQLITE_PAGECACHE_BLOCKALLOC blocks to store/retrieve 
+** a PGroupBlock pointer based on a pointer to a page buffer. 
+*/
+#define PAGE_SET_BLOCKPTR(pCache, pPg, pBlock) \
+  ( *(PGroupBlock **)&(((u8*)pPg)[sizeof(PgHdr1) + pCache->szPage]) = pBlock )
+
+#define PAGE_GET_BLOCKPTR(pCache, pPg) \
+  ( *(PGroupBlock **)&(((u8*)pPg)[sizeof(PgHdr1) + pCache->szPage]) )
+
+
 /*
 ** Macros to enter and leave the PCache LRU mutex.
 */
@@ -283,13 +352,143 @@ static int pcache1MemSize(void *p){
 }
 #endif /* SQLITE_ENABLE_MEMORY_MANAGEMENT */
 
+/*
+** The block pBlock belongs to list pList but is not currently linked in.
+** Insert it into the start of the list.
+*/
+static void addBlockToList(PGroupBlockList *pList, PGroupBlock *pBlock){
+  pBlock->pPrev = 0;
+  pBlock->pNext = pList->pFirst;
+  pList->pFirst = pBlock;
+  if( pBlock->pNext ){
+    pBlock->pNext->pPrev = pBlock;
+  }else{
+    assert( pList->pLast==0 );
+    pList->pLast = pBlock;
+  }
+}
+
+/*
+** If there are no blocks in the list headed by pList, remove pList
+** from the pGroup->pBlockList list and free it with sqlite3_free().
+*/
+static void freeListIfEmpty(PGroup *pGroup, PGroupBlockList *pList){
+  assert( sqlite3_mutex_held(pGroup->mutex) );
+  if( pList->pFirst==0 ){
+    PGroupBlockList **pp;
+    for(pp=&pGroup->pBlockList; *pp!=pList; pp=&(*pp)->pNext);
+    *pp = (*pp)->pNext;
+    sqlite3_free(pList);
+  }
+}
+
 /*
 ** Allocate a new page object initially associated with cache pCache.
 */
 static PgHdr1 *pcache1AllocPage(PCache1 *pCache){
   int nByte = sizeof(PgHdr1) + pCache->szPage;
-  void *pPg = pcache1Alloc(nByte);
+  void *pPg = 0;
   PgHdr1 *p;
+
+#ifdef SQLITE_PAGECACHE_BLOCKALLOC
+  PGroup *pGroup = pCache->pGroup;
+  PGroupBlockList *pList;
+  PGroupBlock *pBlock;
+  int i;
+
+  nByte += sizeof(PGroupBlockList *);
+  nByte = ROUND8(nByte);
+
+  do{
+    for(pList=pGroup->pBlockList; pList; pList=pList->pNext){
+      if( pList->nByte==nByte ) break;
+    }
+    if( pList==0 ){
+      PGroupBlockList *pNew;
+      pcache1LeaveMutex(pCache->pGroup);
+      pNew = (PGroupBlockList *)sqlite3MallocZero(sizeof(PGroupBlockList));
+      pcache1EnterMutex(pCache->pGroup);
+      if( pNew==0 ){
+        /* malloc() failure. Return early. */
+        return 0;
+      }
+      for(pList=pGroup->pBlockList; pList; pList=pList->pNext){
+        if( pList->nByte==nByte ) break;
+      }
+      if( pList ){
+        sqlite3_free(pNew);
+      }else{
+        pNew->nByte = nByte;
+        pNew->pNext = pGroup->pBlockList;
+        pGroup->pBlockList = pNew;
+        pList = pNew;
+      }
+    }
+  }while( pList==0 );
+
+  pBlock = pList->pFirst;
+  if( pBlock==0 || pBlock->mUsed==(((Bitmask)1<<pBlock->nEntry)-1) ){
+    int sz;
+
+    /* Allocate a new block. Try to allocate enough space for the PGroupBlock
+    ** structure and MINENTRY allocations of nByte bytes each. If the 
+    ** allocator returns more memory than requested, then more than MINENTRY 
+    ** allocations may fit in it. */
+    pcache1LeaveMutex(pCache->pGroup);
+    sz = sizeof(PGroupBlock) + PAGECACHE_BLOCKALLOC_MINENTRY * nByte;
+    pBlock = (PGroupBlock *)sqlite3Malloc(sz);
+    pcache1EnterMutex(pCache->pGroup);
+
+    if( !pBlock ){
+      freeListIfEmpty(pGroup, pList);
+      return 0;
+    }
+    pBlock->nEntry = (sqlite3MallocSize(pBlock) - sizeof(PGroupBlock)) / nByte;
+    if( pBlock->nEntry>=BMS ){
+      pBlock->nEntry = BMS-1;
+    }
+    pBlock->pList = pList;
+    pBlock->mUsed = 0;
+    pBlock->aData = (u8 *)&pBlock[1];
+    addBlockToList(pList, pBlock);
+
+    sz = sqlite3MallocSize(pBlock);
+    sqlite3_mutex_enter(pcache1.mutex);
+    sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz);
+    sqlite3_mutex_leave(pcache1.mutex);
+  }
+
+  for(i=0; pPg==0 && ALWAYS(i<pBlock->nEntry); i++){
+    if( 0==(pBlock->mUsed & ((Bitmask)1<<i)) ){
+      pBlock->mUsed |= ((Bitmask)1<<i);
+      pPg = (void *)&pBlock->aData[pList->nByte * i];
+    }
+  }
+  assert( pPg );
+  PAGE_SET_BLOCKPTR(pCache, pPg, pBlock);
+
+  /* If the block is now full, shift it to the end of the list */
+  if( pBlock->mUsed==(((Bitmask)1<<pBlock->nEntry)-1) && pList->pLast!=pBlock ){
+    assert( pList->pFirst==pBlock );
+    assert( pBlock->pPrev==0 );
+    assert( pList->pLast->pNext==0 );
+    pList->pFirst = pBlock->pNext;
+    pList->pFirst->pPrev = 0;
+    pBlock->pPrev = pList->pLast;
+    pBlock->pNext = 0;
+    pList->pLast->pNext = pBlock;
+    pList->pLast = pBlock;
+  }
+#else
+  /* The group mutex must be released before pcache1Alloc() is called. This
+  ** is because it may call sqlite3_release_memory(), which assumes that 
+  ** this mutex is not held. */
+  assert( sqlite3_mutex_held(pCache->pGroup->mutex) );
+  pcache1LeaveMutex(pCache->pGroup);
+  pPg = pcache1Alloc(nByte);
+  pcache1EnterMutex(pCache->pGroup);
+#endif
+
   if( pPg ){
     p = PAGE_TO_PGHDR1(pCache, pPg);
     if( pCache->bPurgeable ){
@@ -311,10 +510,52 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache){
 static void pcache1FreePage(PgHdr1 *p){
   if( ALWAYS(p) ){
     PCache1 *pCache = p->pCache;
+    void *pPg = PGHDR1_TO_PAGE(p);
+
+#ifdef SQLITE_PAGECACHE_BLOCKALLOC
+    PGroupBlock *pBlock = PAGE_GET_BLOCKPTR(pCache, pPg);
+    PGroupBlockList *pList = pBlock->pList;
+    int i = ((u8 *)pPg - pBlock->aData) / pList->nByte;
+
+    assert( pPg==(void *)&pBlock->aData[i*pList->nByte] );
+    assert( pBlock->mUsed & ((Bitmask)1<<i) );
+    pBlock->mUsed &= ~((Bitmask)1<<i);
+
+    /* Remove the block from the list. If it is completely empty, free it.
+    ** Or if it is not completely empty, re-insert it at the start of the
+    ** list. */
+    if( pList->pFirst==pBlock ){
+      pList->pFirst = pBlock->pNext;
+      if( pList->pFirst ) pList->pFirst->pPrev = 0;
+    }else{
+      pBlock->pPrev->pNext = pBlock->pNext;
+    }
+    if( pList->pLast==pBlock ){
+      pList->pLast = pBlock->pPrev;
+      if( pList->pLast ) pList->pLast->pNext = 0;
+    }else{
+      pBlock->pNext->pPrev = pBlock->pPrev;
+    }
+
+    if( pBlock->mUsed==0 ){
+      PGroup *pGroup = p->pCache->pGroup;
+
+      int sz = sqlite3MallocSize(pBlock);
+      sqlite3_mutex_enter(pcache1.mutex);
+      sqlite3StatusAdd(SQLITE_STATUS_PAGECACHE_OVERFLOW, -sz);
+      sqlite3_mutex_leave(pcache1.mutex);
+      freeListIfEmpty(pGroup, pList);
+      sqlite3_free(pBlock);
+    }else{
+      addBlockToList(pList, pBlock);
+    }
+#else
+    assert( sqlite3_mutex_held(p->pCache->pGroup->mutex) );
+    pcache1Free(pPg);
+#endif
     if( pCache->bPurgeable ){
       pCache->pGroup->nCurrentPage--;
     }
-    pcache1Free(PGHDR1_TO_PAGE(p));
   }
 }
 
@@ -752,9 +993,7 @@ static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){
   */
   if( !pPage ){
     if( createFlag==1 ) sqlite3BeginBenignMalloc();
-    pcache1LeaveMutex(pGroup);
     pPage = pcache1AllocPage(pCache);
-    pcache1EnterMutex(pGroup);
     if( createFlag==1 ) sqlite3EndBenignMalloc();
   }