SQLite3源码学习(21) pcache1分析

学习本章之前要先复习以下2篇文章:

SQLite3源码学习(9Page Cache概述

SQLite3源码学习(10testpcache分析

之前讲到page cache是一种可插入式的管理方式,在sqlite3GlobalConfig.pcache2里定义了对page cache管理的一系列方法接口,并且介绍了最简单的一种接口testpcache,现在我们来分析一下默认的接口pache1,这个要比testpcache复杂很多。

1.内存结构

一个page cache在内存中按如下格式存储,由数据内容和头部组成:


 其中PgHdr1pcache1.c里定义,PgHdrpcache.c里定义,MemPagebtree.c里定义,在新建一个页时,上述内容由sqlite3_pcache_page结构体表示

struct sqlite3_pcache_page {
  void *pBuf;        /* The content of the page */
  void *pExtra;      /* Extra information associated with the page */
};

其中pBuf指向database page contentPgHdr1pExtra指向PgHdrMemPage,另外在PgHdr1结构体里定义了一个sqlite3_pcache_page对象。

struct PgHdr1 {
  sqlite3_pcache_page page;      /* Base class. Must be first. pBuf & pExtra */
  ……
};

     新建一个page cache时代码如下:

pPg = pcache1Alloc(pCache->szAlloc);
p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage];
p->page.pBuf = pPg;
p->page.pExtra = &p[1];

2.结构关系

hash

每一次调用pcache1的接口时,需要传入一个sqlite3_pcache*类型的对象作为连接句柄,在pcache1中被转换为PCache1*类型。

有些时候还需要传入页面对象作为参数,传入时的类型是sqlite3_pcache_page*,这是一个基类对象,如上一节所说,在pcahce1中会被扩展成PgHdr1*类型的对象。

PCache1*类型的对象中有一张hash表,所有的page cache都存放在这张hash表里,如果page cachekey值对应的hash表的索引相同,那么相同地址的元素再建立一个链表。


Pcache1中与hash表相关变量如下:

struct PCache1 {
  ……
  int szPage;                         /* Size of database content section */
  int szExtra;                        /* sizeof(MemPage)+sizeof(PgHdr) */
//即szPage+szExtra+sizeof(PgHdr1)
  int szAlloc;                        /* Total size of one pcache line */
  ……
  //hash表中最大的关键字,即最大的页面序号
  unsigned int iMaxKey;               /* Largest key seen since xTruncate() */
  //包括hash表元素和所有链表元素的总个数
  unsigned int nPage;                 /* Total number of pages in apHash */
  //即apHash数组的长度
  unsigned int nHash;                 /* Number of slots in apHash[] */
  PgHdr1 **apHash;                    /* Hash table for fast lookup by key 
};

PGroup

  

我们把存放page cache的地址称作slot,那么上节讲到的hash表就把这些slot很好地组织在了一起,从而更容易查找对应的缓存页。

这些需要经常用到的页缓存我们把它标记为pinned,不常用的缓存页我们把它标记为unpinned。我们还可以通过一种叫做PGroup的方式把这些unpinned slot组织在一起,这个是LRU算法的基础。也就是说当缓存页数量已经达到最大时,需要清理掉一些不常用的缓存页来增加新的缓存页。

PGroup的实现有2种模式:

模式1

每一个连接的PCache拥有自己独立的PGroup,这个时候不需要加锁,访问速度更快,但是占用的内存空间更大。

模式2

所有连接的PCache共有一个PGroup,也就是说所有PCacheunppined page组成一个PGroup,这时候PGroup属于多线程中的共享资源,需要加锁,所以速度慢一点,但是这种模式可以回收利用更多的内存空间。

 

PGroup的构建方式如下图所示:


所有的unpinned page组成一个双向的循环链表,pGroup->lru作为这个链表的表头。其实这相当于一个队列,新插入的page加入到队列头部,在队列尾部的page是最早的,所以回收时先回收队列尾部的page。用循环列表就不用查找操作,只要知道了pGroup->lru,就能定位到队列的头部和尾部。

pGroup相关数据结构如下:

struct PCache1 {
  /* Cache configuration parameters. Page size (szPage) and the purgeable
  ** flag (bPurgeable) are set when the cache is created. nMax may be 
  ** modified at any time by a call to the pcache1Cachesize() method.
  ** The PGroup mutex must be held when accessing nMax.
  */
  PGroup *pGroup;                     /* PGroup this cache belongs to */
 ……
 /* 如果该值为0,那么该PCache的所有page都不可回收利用 */
  int bPurgeable;                     /* True if cache is purgeable */
  //每个PCache预留的slot数量,当前为10
  unsigned int nMin;                  /* Minimum number of pages reserved */
  //每个PCache配置的最大slot数量
  unsigned int nMax;                  /* Configured "cache_size" value */
  unsigned int n90pct;                /* nMax*9/10 */

  
  unsigned int nRecyclable;           /* Number of pages in the LRU list */
……
};

struct PGroup {
  //在模式1时锁为空
  sqlite3_mutex *mutex;          /* MUTEX_STATIC_LRU or NULL */
  //所有pCache.nMax之和
  unsigned int nMaxPage;         /* Sum of nMax for purgeable caches */
  //所有pCache.nMin之和
  unsigned int nMinPage;         /* Sum of nMin for purgeable caches */
 //在createFlag==1时,最大使用的slot数量
 //预留nMaxpage- mxPinned= nMinPage-10数量的slot
  unsigned int mxPinned;         /* nMaxpage + 10 - nMinPage */
  unsigned int nCurrentPage;     /* Number of purgeable pages allocated */
  PgHdr1 lru;                    /* The beginning and end of the LRU list */
};

3.内存申请

page cache中,申请内存主要由以下3种方式:

1.PCache-local bulk分配器

这个针对pGroup的模式1,也就是先申请一个大的zBulk空间,然后将其分割成一个个slot,每个slot按照内存结构关系定义好,再把这些slot组成一个链表,使用时只要从头部摘下即可,不用了放回头部。

2.页缓存内存分配器

这个针对pGroup的模式2,缺省时是关闭的,需要调用 sqlite3_config(SQLITE_CONFIG_PAGECACHE, pBuf, sz, N)接口来配置

其中pBuf是申请的空间地址,szslot大小,Nslot个数,申请的空间再通过sqlite3PcacheBufferSetup()函数配置,这里也是把一大块地址分割成许多个slot再组成链表,放到pcache1.pFree,但是slot的格式并没有定义,这是因为针对不同的PCache,每个页缓存的szAlloc可能会有所不同。

3.普通内存分配器

当以上2种方式都没有申请到内存时,调用sqlite3Malloc()

4.Page的读取

如果pGroup是模式1,那么调用pcache1FetchWithMutex()加锁,如果pGroup是模式2,那么直接调用pcache1FetchNoMutex()

读取一个page按照以下流程:

1.根据页号(iKey)搜索hash表

static PgHdr1 *pcache1FetchNoMutex(
  sqlite3_pcache *p, 
  unsigned int iKey, 
  int createFlag
){
  ……
  PCache1 *pCache = (PCache1 *)p;
  PgHdr1 *pPage = 0;
  pPage = pCache->apHash[iKey % pCache->nHash];
  while( pPage && pPage->iKey!=iKey ){ pPage = pPage->pNext; }
  ……
}

2.如果页面找到了,那么返回这个页面;如果没找到,并且createFlag0,那么返回异常;如果没找到,但是createFlag不为0,继续以下步骤。

3.如果createFlag==1,并且使用的page已经超过最大限制,或者内存紧缺,那么直接返回0

 unsigned int nPinned;
  PGroup *pGroup = pCache->pGroup;
  if( createFlag==1 && (
        nPinned>=pGroup->mxPinned
     || nPinned>=pCache->n90pct
     || (pcache1UnderMemoryPressure(pCache) && pCache->nRecyclable<nPinned)
  )){
    return 0;
  }

因为通常步骤3以后是不需要的,所以把以后的步骤单独放在pcache1FetchStage2()函数里,并且设置强制不内联,以减少函数堆栈的初始化,加快读取速度。

4.如果满足条件,回收利用unpinned page

 if( pCache->bPurgeable//可回收
   //循环链表的表头不能被回收
   && !pGroup->lru.pLruPrev->isAnchor
   //当使用的page超过了设置的最大值或者内存不足才回收
   && ((pCache->nPage+1>=pCache->nMax) || pcache1UnderMemoryPressure(pCache))
  ){
    PCache1 *pOther;
    pPage = pGroup->lru.pLruPrev;//回收队列尾部的page
    assert( pPage->isPinned==0 );
    pcache1RemoveFromHash(pPage, 0);//把它从原来的hash表中移除
    pcache1PinPage(pPage);//标记为pinned
    pOther = pPage->pCache;
    if( pOther->szAlloc != pCache->szAlloc ){
      //回收的slot长度不符合要求
      pcache1FreePage(pPage);
      pPage = 0;
    }else{
      //其实相当于bPurgeable为0,那么nCurrentPage++
      pGroup->nCurrentPage -= (pOther->bPurgeable - pCache->bPurgeable);
    }
  }

5.经过上面步骤还没找到page,那么重新申请一个page cache

5.函数说明

Ÿ   void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n)

配置页缓存内存分配器。

Ÿ   static int pcache1InitBulk(PCache1 *pCache)

初始化bulk内存分配器

Ÿ   static void *pcache1Alloc(int nByte)

为一个缓存页申请nByte大小的空间,先使用页缓存内存分配器,如果内存不够分配,再使用通用内存分配器。

Ÿ   static void pcache1Free(void *p)

  释放缓存页

Ÿ   static int pcache1MemSize(void *p)

获取申请内存的长度

Ÿ   static PgHdr1 *pcache1AllocPage(PCache1 *pCache, int benignMalloc)

创建一个新的缓存页,如果不是通过bulk内存分配器获得内存,那么需要对申请的slot定义缓存页的内存结构

Ÿ   void *sqlite3PageMalloc(int sz)

pcache1Alloc()的一个对外接口

Ÿ   static void pcache1FreePage(PgHdr1 *p)

pcache1Free()的一个对外接口

Ÿ   static void pcache1ResizeHash(PCache1 *p)

pCache->nPage>=pCache->nHash时,把hash表长度扩大1倍,并重新调整hash表结构

Ÿ   static PgHdr1 *pcache1PinPage(PgHdr1 *pPage)

把刚创建或刚回收的缓存页标记为pinned

Ÿ   static void pcache1RemoveFromHash(PgHdr1 *pPage, int freeFlag)

pPagehash表中移除,如果freeFlag1,那么释放内存

Ÿ   static void pcache1EnforceMaxPage(PCache1 *pCache)

如果pGroup->nCurrentPage>pGroup->nMaxPage,那么移除LRU队列中多余的page

Ÿ   static void pcache1TruncateUnsafe(

  PCache1 *pCache,             /* The cache to truncate */

  unsigned int iLimit          /* Drop pages with this pgno or larger */

)

释放页号大于iLimit的页,如果pCache->iMaxKey - iLimit < pCache->nHash,那么不用扫描整个hash表,否则从pCache->nHash/2处开始扫描整个hash表。

Ÿ   static int pcache1Init(void *NotUsed)

    设置PGroup模式,如果是模式2,初始化锁。

Ÿ   static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable)

创建一个pCache,并初始化相关参数

Ÿ   static void pcache1Cachesize(sqlite3_pcache *p, int nMax)

设置pCache->nMax

Ÿ   static void pcache1Shrink(sqlite3_pcache *p)

把所有unpinned page都释放掉

Ÿ   static int pcache1Pagecount(sqlite3_pcache *p)

获得当前缓存页的数量

Ÿ   static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2(

  PCache1 *pCache,

  unsigned int iKey,

  int createFlag

)

读取缓存页,见上节分析

Ÿ   static PgHdr1 *pcache1FetchNoMutex(

  sqlite3_pcache *p,

  unsigned int iKey,

  int createFlag

)

读取缓存页,见上节分析

Ÿ   static PgHdr1 *pcache1FetchWithMutex(

  sqlite3_pcache *p,

  unsigned int iKey,

  int createFlag

)

读取缓存页,需要先加锁

Ÿ   static sqlite3_pcache_page *pcache1Fetch(

  sqlite3_pcache *p,

  unsigned int iKey,

  int createFlag

)

读取缓存页的对外接口

Ÿ   static void pcache1Unpin(

  sqlite3_pcache *p,

  sqlite3_pcache_page *pPg,

  int reuseUnlikely

)

page插入到LRU队列里

Ÿ   static void pcache1Rekey(

  sqlite3_pcache *p,

  sqlite3_pcache_page *pPg,

  unsigned int iOld,

  unsigned int iNew

)

重新设置page的页号

Ÿ   static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit)

加锁后再调用pcache1TruncateUnsafe()

Ÿ   static void pcache1Destroy(sqlite3_pcache *p)

释放pCache中的所有页

Ÿ   void sqlite3PCacheSetDefault(void)

设置pcache1的对外接口到sqlite3GlobalConfig.pcache2

Ÿ   int sqlite3PcacheReleaseMemory(int nReq)

PGroup里释放nReq大小的空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值