persistentstorage/sqlite3api/SQLite/mem6.c
changeset 0 08ec8eefde2f
equal deleted inserted replaced
-1:000000000000 0:08ec8eefde2f
       
     1 /*
       
     2 ** 2008 July 24
       
     3 **
       
     4 ** The author disclaims copyright to this source code.  In place of
       
     5 ** a legal notice, here is a blessing:
       
     6 **
       
     7 **    May you do good and not evil.
       
     8 **    May you find forgiveness for yourself and forgive others.
       
     9 **    May you share freely, never taking more than you give.
       
    10 **
       
    11 *************************************************************************
       
    12 **
       
    13 ** This file contains an alternative memory allocation system for SQLite.
       
    14 ** This system is implemented as a wrapper around the system provided
       
    15 ** by the operating system - vanilla malloc(), realloc() and free().
       
    16 **
       
    17 ** This system differentiates between requests for "small" allocations 
       
    18 ** (by default those of 128 bytes or less) and "large" allocations (all
       
    19 ** others). The 256 byte threshhold is configurable at runtime.
       
    20 **
       
    21 ** All requests for large allocations are passed through to the 
       
    22 ** default system.
       
    23 **
       
    24 ** Requests for small allocations are met by allocating space within
       
    25 ** one or more larger "chunks" of memory obtained from the default
       
    26 ** memory allocation system. Chunks of memory are usually 64KB or 
       
    27 ** larger. The algorithm used to manage space within each chunk is
       
    28 ** the same as that used by mem5.c. 
       
    29 **
       
    30 ** This strategy is designed to prevent the default memory allocation
       
    31 ** system (usually the system malloc) from suffering from heap 
       
    32 ** fragmentation. On some systems, heap fragmentation can cause a 
       
    33 ** significant real-time slowdown.
       
    34 **
       
    35 ** $Id: mem6.c,v 1.10 2008/09/02 17:52:52 danielk1977 Exp $
       
    36 */
       
    37 
       
    38 #ifdef SQLITE_ENABLE_MEMSYS6
       
    39 
       
    40 #include "sqliteInt.h"
       
    41 
       
    42 /*
       
    43 ** Maximum size of any "small" allocation is ((1<<LOGMAX)*Mem6Chunk.nAtom).
       
    44 ** Mem6Chunk.nAtom is always at least 8, so this is not a practical
       
    45 ** limitation
       
    46 */
       
    47 #define LOGMAX 30
       
    48 
       
    49 /*
       
    50 ** Default value for the "small" allocation size threshold.
       
    51 */
       
    52 #define SMALL_MALLOC_DEFAULT_THRESHOLD 256
       
    53 
       
    54 /*
       
    55 ** Minimum size for a memory chunk.
       
    56 */
       
    57 #define MIN_CHUNKSIZE (1<<16)
       
    58 
       
    59 #define LOG2_MINALLOC 4
       
    60 
       
    61 
       
    62 typedef struct Mem6Chunk Mem6Chunk;
       
    63 typedef struct Mem6Link Mem6Link;
       
    64 
       
    65 /*
       
    66 ** A minimum allocation is an instance of the following structure.
       
    67 ** Larger allocations are an array of these structures where the
       
    68 ** size of the array is a power of 2.
       
    69 */
       
    70 struct Mem6Link {
       
    71   int next;       /* Index of next free chunk */
       
    72   int prev;       /* Index of previous free chunk */
       
    73 };
       
    74 
       
    75 /*
       
    76 ** Masks used for mem5.aCtrl[] elements.
       
    77 */
       
    78 #define CTRL_LOGSIZE  0x1f    /* Log2 Size of this block relative to POW2_MIN */
       
    79 #define CTRL_FREE     0x20    /* True if not checked out */
       
    80 
       
    81 struct Mem6Chunk {
       
    82   Mem6Chunk *pNext;
       
    83 
       
    84   /*
       
    85   ** Lists of free blocks of various sizes.
       
    86   */
       
    87   int aiFreelist[LOGMAX+1];
       
    88 
       
    89   int nCheckedOut; /* Number of currently outstanding allocations */
       
    90 
       
    91   /*
       
    92   ** Space for tracking which blocks are checked out and the size
       
    93   ** of each block. One byte per block.
       
    94   */
       
    95   u8 *aCtrl;
       
    96 
       
    97   /*
       
    98   ** Memory available for allocation
       
    99   */
       
   100   int nAtom;       /* Smallest possible allocation in bytes */
       
   101   int nBlock;      /* Number of nAtom sized blocks in zPool */
       
   102   u8 *zPool;       /* Pointer to memory chunk from which allocations are made */
       
   103 };
       
   104 
       
   105 #define MEM6LINK(idx) ((Mem6Link *)(&pChunk->zPool[(idx)*pChunk->nAtom]))
       
   106 
       
   107 static SQLITE_WSD struct Mem6Global {
       
   108   int nMinAlloc;                  /* Minimum allowed allocation size */
       
   109   int nThreshold;                 /* Allocs larger than this go to malloc() */
       
   110   int nLogThreshold;              /* log2 of (nThreshold/nMinAlloc) */
       
   111   sqlite3_mutex *mutex;
       
   112   Mem6Chunk *pChunk;              /* Singly linked list of all memory chunks */
       
   113 } mem6 = { 48642791 };
       
   114 
       
   115 #define mem6 GLOBAL(struct Mem6Global, mem6)
       
   116 
       
   117 /*
       
   118 ** Unlink the chunk at pChunk->aPool[i] from list it is currently
       
   119 ** on.  It should be found on pChunk->aiFreelist[iLogsize].
       
   120 */
       
   121 static void memsys6Unlink(Mem6Chunk *pChunk, int i, int iLogsize){
       
   122   int next, prev;
       
   123   assert( i>=0 && i<pChunk->nBlock );
       
   124   assert( iLogsize>=0 && iLogsize<=mem6.nLogThreshold );
       
   125   assert( (pChunk->aCtrl[i] & CTRL_LOGSIZE)==iLogsize );
       
   126 
       
   127   next = MEM6LINK(i)->next;
       
   128   prev = MEM6LINK(i)->prev;
       
   129   if( prev<0 ){
       
   130     pChunk->aiFreelist[iLogsize] = next;
       
   131   }else{
       
   132     MEM6LINK(prev)->next = next;
       
   133   }
       
   134   if( next>=0 ){
       
   135     MEM6LINK(next)->prev = prev;
       
   136   }
       
   137 }
       
   138 
       
   139 /*
       
   140 ** Link the chunk at mem5.aPool[i] so that is on the iLogsize
       
   141 ** free list.
       
   142 */
       
   143 static void memsys6Link(Mem6Chunk *pChunk, int i, int iLogsize){
       
   144   int x;
       
   145   assert( i>=0 && i<pChunk->nBlock );
       
   146   assert( iLogsize>=0 && iLogsize<=mem6.nLogThreshold );
       
   147   assert( (pChunk->aCtrl[i] & CTRL_LOGSIZE)==iLogsize );
       
   148 
       
   149   x = MEM6LINK(i)->next = pChunk->aiFreelist[iLogsize];
       
   150   MEM6LINK(i)->prev = -1;
       
   151   if( x>=0 ){
       
   152     assert( x<pChunk->nBlock );
       
   153     MEM6LINK(x)->prev = i;
       
   154   }
       
   155   pChunk->aiFreelist[iLogsize] = i;
       
   156 }
       
   157 
       
   158 
       
   159 /*
       
   160 ** Find the first entry on the freelist iLogsize.  Unlink that
       
   161 ** entry and return its index. 
       
   162 */
       
   163 static int memsys6UnlinkFirst(Mem6Chunk *pChunk, int iLogsize){
       
   164   int i;
       
   165   int iFirst;
       
   166 
       
   167   assert( iLogsize>=0 && iLogsize<=mem6.nLogThreshold );
       
   168   i = iFirst = pChunk->aiFreelist[iLogsize];
       
   169   assert( iFirst>=0 );
       
   170   memsys6Unlink(pChunk, iFirst, iLogsize);
       
   171   return iFirst;
       
   172 }
       
   173 
       
   174 static int roundupLog2(int n){
       
   175   static const char LogTable256[256] = {
       
   176     0,                                                    /* 1 */
       
   177     1,                                                    /* 2 */
       
   178     2, 2,                                                 /* 3..4 */
       
   179     3, 3, 3, 3,                                           /* 5..8 */
       
   180     4, 4, 4, 4, 4, 4, 4, 4,                               /* 9..16 */
       
   181     5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,       /* 17..32 */
       
   182     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
       
   183     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,       /* 33..64 */
       
   184     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
       
   185     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
       
   186     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
       
   187     7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,       /* 65..128 */
       
   188     8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
       
   189     8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
       
   190     8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
       
   191     8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
       
   192     8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
       
   193     8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
       
   194     8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
       
   195     8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,       /* 129..256 */
       
   196   };
       
   197 
       
   198   assert(n<=(1<<16) && n>0);
       
   199   if( n<=256 ) return LogTable256[n-1];
       
   200   return LogTable256[(n>>8) - ((n&0xFF)?0:1)] + 8;
       
   201 }
       
   202 
       
   203 /*
       
   204 ** Allocate and return a block of (pChunk->nAtom << iLogsize) bytes from chunk
       
   205 ** pChunk. If the allocation request cannot be satisfied, return 0.
       
   206 */
       
   207 static void *chunkMalloc(Mem6Chunk *pChunk, int iLogsize){
       
   208   int i;           /* Index of a mem5.aPool[] slot */
       
   209   int iBin;        /* Index into mem5.aiFreelist[] */
       
   210 
       
   211   /* Make sure mem5.aiFreelist[iLogsize] contains at least one free
       
   212   ** block.  If not, then split a block of the next larger power of
       
   213   ** two in order to create a new free block of size iLogsize.
       
   214   */
       
   215   for(iBin=iLogsize; pChunk->aiFreelist[iBin]<0 && iBin<=mem6.nLogThreshold; iBin++){}
       
   216   if( iBin>mem6.nLogThreshold ) return 0;
       
   217   i = memsys6UnlinkFirst(pChunk, iBin);
       
   218   while( iBin>iLogsize ){
       
   219     int newSize;
       
   220     iBin--;
       
   221     newSize = 1 << iBin;
       
   222     pChunk->aCtrl[i+newSize] = CTRL_FREE | iBin;
       
   223     memsys6Link(pChunk, i+newSize, iBin);
       
   224   }
       
   225   pChunk->aCtrl[i] = iLogsize;
       
   226 
       
   227   /* Return a pointer to the allocated memory. */
       
   228   pChunk->nCheckedOut++;
       
   229   return (void*)&pChunk->zPool[i*pChunk->nAtom];
       
   230 }
       
   231 
       
   232 /*
       
   233 ** Free the allocation pointed to by p, which is guaranteed to be non-zero
       
   234 ** and a part of chunk object pChunk.
       
   235 */
       
   236 static void chunkFree(Mem6Chunk *pChunk, void *pOld){
       
   237   u32 size, iLogsize;
       
   238   int iBlock;             
       
   239 
       
   240   /* Set iBlock to the index of the block pointed to by pOld in 
       
   241   ** the array of pChunk->nAtom byte blocks pointed to by pChunk->zPool.
       
   242   */
       
   243   iBlock = ((u8 *)pOld-pChunk->zPool)/pChunk->nAtom;
       
   244 
       
   245   /* Check that the pointer pOld points to a valid, non-free block. */
       
   246   assert( iBlock>=0 && iBlock<pChunk->nBlock );
       
   247   assert( ((u8 *)pOld-pChunk->zPool)%pChunk->nAtom==0 );
       
   248   assert( (pChunk->aCtrl[iBlock] & CTRL_FREE)==0 );
       
   249 
       
   250   iLogsize = pChunk->aCtrl[iBlock] & CTRL_LOGSIZE;
       
   251   size = 1<<iLogsize;
       
   252   assert( iBlock+size-1<pChunk->nBlock );
       
   253 
       
   254   pChunk->aCtrl[iBlock] |= CTRL_FREE;
       
   255   pChunk->aCtrl[iBlock+size-1] |= CTRL_FREE;
       
   256 
       
   257   pChunk->aCtrl[iBlock] = CTRL_FREE | iLogsize;
       
   258   while( iLogsize<mem6.nLogThreshold ){
       
   259     int iBuddy;
       
   260     if( (iBlock>>iLogsize) & 1 ){
       
   261       iBuddy = iBlock - size;
       
   262     }else{
       
   263       iBuddy = iBlock + size;
       
   264     }
       
   265     assert( iBuddy>=0 );
       
   266     if( (iBuddy+(1<<iLogsize))>pChunk->nBlock ) break;
       
   267     if( pChunk->aCtrl[iBuddy]!=(CTRL_FREE | iLogsize) ) break;
       
   268     memsys6Unlink(pChunk, iBuddy, iLogsize);
       
   269     iLogsize++;
       
   270     if( iBuddy<iBlock ){
       
   271       pChunk->aCtrl[iBuddy] = CTRL_FREE | iLogsize;
       
   272       pChunk->aCtrl[iBlock] = 0;
       
   273       iBlock = iBuddy;
       
   274     }else{
       
   275       pChunk->aCtrl[iBlock] = CTRL_FREE | iLogsize;
       
   276       pChunk->aCtrl[iBuddy] = 0;
       
   277     }
       
   278     size *= 2;
       
   279   }
       
   280   pChunk->nCheckedOut--;
       
   281   memsys6Link(pChunk, iBlock, iLogsize);
       
   282 }
       
   283 
       
   284 /*
       
   285 ** Return the actual size of the block pointed to by p, which is guaranteed
       
   286 ** to have been allocated from chunk pChunk.
       
   287 */
       
   288 static int chunkSize(Mem6Chunk *pChunk, void *p){
       
   289   int iSize = 0;
       
   290   if( p ){
       
   291     int i = ((u8 *)p-pChunk->zPool)/pChunk->nAtom;
       
   292     assert( i>=0 && i<pChunk->nBlock );
       
   293     iSize = pChunk->nAtom * (1 << (pChunk->aCtrl[i]&CTRL_LOGSIZE));
       
   294   }
       
   295   return iSize;
       
   296 }
       
   297 
       
   298 /*
       
   299 ** Return true if there are currently no outstanding allocations.
       
   300 */
       
   301 static int chunkIsEmpty(Mem6Chunk *pChunk){
       
   302   return (pChunk->nCheckedOut==0);
       
   303 }
       
   304 
       
   305 /*
       
   306 ** Initialize the buffer zChunk, which is nChunk bytes in size, as
       
   307 ** an Mem6Chunk object. Return a copy of the zChunk pointer.
       
   308 */
       
   309 static Mem6Chunk *chunkInit(u8 *zChunk, int nChunk, int nMinAlloc){
       
   310   int ii;
       
   311   int iOffset;
       
   312   Mem6Chunk *pChunk = (Mem6Chunk *)zChunk;
       
   313 
       
   314   assert( nChunk>sizeof(Mem6Chunk) );
       
   315   assert( nMinAlloc>sizeof(Mem6Link) );
       
   316 
       
   317   memset(pChunk, 0, sizeof(Mem6Chunk));
       
   318   pChunk->nAtom = nMinAlloc;
       
   319   pChunk->nBlock = ((nChunk-sizeof(Mem6Chunk)) / (pChunk->nAtom+sizeof(u8)));
       
   320 
       
   321   pChunk->zPool = (u8 *)&pChunk[1];
       
   322   pChunk->aCtrl = &pChunk->zPool[pChunk->nBlock*pChunk->nAtom];
       
   323 
       
   324   for(ii=0; ii<=mem6.nLogThreshold; ii++){
       
   325     pChunk->aiFreelist[ii] = -1;
       
   326   }
       
   327 
       
   328   iOffset = 0;
       
   329   for(ii=mem6.nLogThreshold; ii>=0; ii--){
       
   330     int nAlloc = (1<<ii);
       
   331     while( (iOffset+nAlloc)<=pChunk->nBlock ){
       
   332       pChunk->aCtrl[iOffset] = ii | CTRL_FREE;
       
   333       memsys6Link(pChunk, iOffset, ii);
       
   334       iOffset += nAlloc;
       
   335     }
       
   336   }
       
   337 
       
   338   return pChunk;
       
   339 }
       
   340 
       
   341 
       
   342 static void mem6Enter(void){
       
   343   sqlite3_mutex_enter(mem6.mutex);
       
   344 }
       
   345 
       
   346 static void mem6Leave(void){
       
   347   sqlite3_mutex_leave(mem6.mutex);
       
   348 }
       
   349 
       
   350 /*
       
   351 ** Based on the number and size of the currently allocated chunks, return
       
   352 ** the size of the next chunk to allocate, in bytes.
       
   353 */
       
   354 static int nextChunkSize(void){
       
   355   int iTotal = MIN_CHUNKSIZE;
       
   356   Mem6Chunk *p;
       
   357   for(p=mem6.pChunk; p; p=p->pNext){
       
   358     iTotal = iTotal*2;
       
   359   }
       
   360   return iTotal;
       
   361 }
       
   362 
       
   363 static void freeChunk(Mem6Chunk *pChunk){
       
   364   Mem6Chunk **pp = &mem6.pChunk;
       
   365   for( pp=&mem6.pChunk; *pp!=pChunk; pp = &(*pp)->pNext );
       
   366   *pp = (*pp)->pNext;
       
   367   free(pChunk);
       
   368 }
       
   369 
       
   370 static void *memsys6Malloc(int nByte){
       
   371   Mem6Chunk *pChunk;
       
   372   void *p = 0;
       
   373   int nTotal = nByte+8;
       
   374   int iOffset = 0;
       
   375 
       
   376   if( nTotal>mem6.nThreshold ){
       
   377     p = malloc(nTotal);
       
   378   }else{
       
   379     int iLogsize = 0;
       
   380     if( nTotal>(1<<LOG2_MINALLOC) ){
       
   381       iLogsize = roundupLog2(nTotal) - LOG2_MINALLOC;
       
   382     }
       
   383     mem6Enter();
       
   384     for(pChunk=mem6.pChunk; pChunk; pChunk=pChunk->pNext){
       
   385       p = chunkMalloc(pChunk, iLogsize);
       
   386       if( p ){
       
   387         break;
       
   388       }
       
   389     }
       
   390     if( !p ){
       
   391       int iSize = nextChunkSize();
       
   392       p = malloc(iSize);
       
   393       if( p ){
       
   394         pChunk = chunkInit((u8 *)p, iSize, mem6.nMinAlloc);
       
   395         pChunk->pNext = mem6.pChunk;
       
   396         mem6.pChunk = pChunk;
       
   397         p = chunkMalloc(pChunk, iLogsize);
       
   398         assert(p);
       
   399       }
       
   400     }
       
   401     iOffset = ((u8*)p - (u8*)pChunk);
       
   402     mem6Leave();
       
   403   }
       
   404 
       
   405   if( !p ){
       
   406     return 0;
       
   407   }
       
   408   ((u32 *)p)[0] = iOffset;
       
   409   ((u32 *)p)[1] = nByte;
       
   410   return &((u32 *)p)[2];
       
   411 }
       
   412 
       
   413 static int memsys6Size(void *pPrior){
       
   414   if( pPrior==0 ) return 0;
       
   415   return ((u32*)pPrior)[-1];
       
   416 }
       
   417 
       
   418 static void memsys6Free(void *pPrior){
       
   419   int iSlot;
       
   420   void *p = &((u32 *)pPrior)[-2];
       
   421   iSlot = ((u32 *)p)[0];
       
   422   if( iSlot ){
       
   423     Mem6Chunk *pChunk;
       
   424     mem6Enter();
       
   425     pChunk = (Mem6Chunk *)(&((u8 *)p)[-1 * iSlot]);
       
   426     chunkFree(pChunk, p);
       
   427     if( chunkIsEmpty(pChunk) ){
       
   428       freeChunk(pChunk);
       
   429     }
       
   430     mem6Leave();
       
   431   }else{
       
   432     free(p);
       
   433   }
       
   434 }
       
   435 
       
   436 static void *memsys6Realloc(void *p, int nByte){
       
   437   void *p2;
       
   438 
       
   439   if( p && nByte<=memsys6Size(p) ){
       
   440     p2 = p;
       
   441   }else{
       
   442     p2 = memsys6Malloc(nByte);
       
   443     if( p && p2 ){
       
   444       memcpy(p2, p, memsys6Size(p));
       
   445       memsys6Free(p);
       
   446     }
       
   447   }
       
   448 
       
   449   return p2;
       
   450 }
       
   451 
       
   452 static int memsys6Roundup(int n){
       
   453   if( n>mem6.nThreshold ){
       
   454     return n;
       
   455   }else{
       
   456     return (1<<roundupLog2(n));
       
   457   }
       
   458 }
       
   459 
       
   460 static int memsys6Init(void *pCtx){
       
   461   u8 bMemstat = sqlite3GlobalConfig.bMemstat;
       
   462   mem6.nMinAlloc = (1 << LOG2_MINALLOC);
       
   463   mem6.pChunk = 0;
       
   464   mem6.nThreshold = sqlite3GlobalConfig.nSmall;
       
   465   if( mem6.nThreshold<=0 ){
       
   466     mem6.nThreshold = SMALL_MALLOC_DEFAULT_THRESHOLD;
       
   467   }
       
   468   mem6.nLogThreshold = roundupLog2(mem6.nThreshold) - LOG2_MINALLOC;
       
   469   if( !bMemstat ){
       
   470     mem6.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
       
   471   }
       
   472   return SQLITE_OK;
       
   473 }
       
   474 
       
   475 static void memsys6Shutdown(void *pCtx){
       
   476   memset(&mem6, 0, sizeof(mem6));
       
   477 }
       
   478 
       
   479 /*
       
   480 ** This routine is the only routine in this file with external 
       
   481 ** linkage. It returns a pointer to a static sqlite3_mem_methods
       
   482 ** struct populated with the memsys6 methods.
       
   483 */
       
   484 const sqlite3_mem_methods *sqlite3MemGetMemsys6(void){
       
   485   static const sqlite3_mem_methods memsys6Methods = {
       
   486      memsys6Malloc,
       
   487      memsys6Free,
       
   488      memsys6Realloc,
       
   489      memsys6Size,
       
   490      memsys6Roundup,
       
   491      memsys6Init,
       
   492      memsys6Shutdown,
       
   493      0
       
   494   };
       
   495   return &memsys6Methods;
       
   496 }
       
   497 
       
   498 #endif