Sqlite PCache1原理

概述

实现了页号和内存的映射管理,页缓存的回收与分配

基础概念

Cache Line :缓存行,除了页缓存来存储页数据之外,还需要额外的内存(PgHdr1、PgHdr等)来管理这些页缓存。页缓存 加上这些额外的内存就叫做缓存行。

Sqlite 有一个全局页缓存管理结构体PCacheGlobal,用于管理页缓存。PCacheGlobal 在数据库启动时初始化。

每个链接(操作 db的句柄)有一个局部的页缓存管理结构PCache1,每一个PCache1都会申请一个Bulk(一些缓存行)。缓存行个数取决于Min(PCacheGlobal.nInitPage, PCache1.nMax)。PCache1有两种工作模式:
(1)每个PCache1独自管理自己的LRU链表,会有单独的PGroup。
(2)所有PCache1都从属于同一个PGroup,由这个PGroup统一管理LRU链表。
独立的PGroup意味着没有竞争资源,不需要加锁,同时也意味着,不能使用其他PCache1的未使用的缓存页。因此模式1性能更好,但更耗内存。

PIN页是短期内不能被换入换出的页。

当PCacheGlobal.nFreeSlot < PCacheGlobal.nReserve时,称PCache处于内存压力下。只有在某些条件下才能申请新页,

基础原理

内存布局

PCache1首先会申请一块大内存用于存放页缓存、PgHdr1和额外的内存(本模块不需要关注额外的内存,知道有这么个东西就行),布局如下:
Page | PgHdr1 | extra | Page | PgHdr1 | extra …

PCacheGlobal 使用PCacheGlobal->pFree 指向空闲页链表的首页。
内存布局:
PgFreeslot | reserve mem | PgFreeSlot | reserve mem | …

两者管理空闲页链表的方式不一样,是因为PCacheGlobal管理着多个PCache1,每个PCache1的页大小可能不一样

哈希查找机制

PCache1提供了哈希查找机制,可以快速通过页号找到对应的页缓存。
PCache1申请了长度为nHash的PgHdr1 指针数组apHash。使用index = pgNo % nHash 找到数组下标, apHash[index]就是所需要找的PgHdr1指针(不存在哈希冲突时)。
缓存页哈希表采用链式扩展的方式来解决哈希冲突,如果两个页号哈希后有冲突,使用PgHdr1->pNext来找到下一个冲突的缓存页描述符
在这里插入图片描述
哈希表长度的动态扩展:初始化时,nHash = 256, 当缓存页个数超过256时,就重新申请一块长度为nHash * 2 的内存,并将原哈希表的页重新哈希并插入到新的哈希表

缓存页分配与回收机制

缓存页的分配顺序:先从PCache1中分配,当PCache1的缓存页个数不足时,再从PCacheGlobal 中分配,PCacheGlobal 不足的话就malloc一个新页
缓存页的回收用PGroup机制来回收,所有PCache1的可回收页都在PGroup的LRU链表上,也就是说有可能回收到其他PCache1的页缓存。

结构体

PgHdr1

缓存页描述符

struct PgHdr1 {
  sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */
  unsigned int iKey;        // page 序号
  u16 isBulkLocal;         // 缓存页的归属,是上下文的还是管理结构的
  u16 isAnchor;            // 是不是LRU双向循环链表的带头结点
  PgHdr1 *pNext;           // 如果当前页是空闲页,是下一个空闲页,否则是哈希表上映射到同一个entry的链表下一跳
  PCache1 *pCache;         // 缓存页所属的上下文
  PgHdr1 *pLruNext;         // LRU 链表下一跳
  PgHdr1 *pLruPrev;         // LRU 链表上一跳
};

PGroup

缓存页管理组

struct PGroup {
  sqlite3_mutex *mutex;          /* MUTEX_STATIC_LRU or NULL */
  unsigned int nMaxPage;         // PGroup最多可拥有缓存页的个数,当PCache1的最大缓存页个数发生变化时,需要将LRU链表中多余的页释放掉。
  unsigned int nMinPage;      // 最少需要保留的缓存页个数
  unsigned int mxPinned;         /* nMaxpage + 10 - nMinPage */
  unsigned int nPurgeable;       // LRU链表缓存页的个数
  PgHdr1 lru;                   // LRU 双向循环链表的带头节点
};

PCache1

局部的缓存页管理结构体

struct PCache1 {
  PGroup *pGroup;                     /* PGroup this cache belongs to */
  unsigned int *pnPurgeable;        // 当前PCache1管理的页是可换入换出时为&pGroup->nPurgeable,否则为&PCache1->nPurgeableDummy
  int szPage;                         // 数据页大小
  int szExtra;                        // 管理单个缓存页需要额外的内存大小, sizeof(MemPage)+sizeof(PgHdr) 
  int szAlloc;       				   // 申请一个缓存页需要的内存大小,szPage + szExtra + sizeof(PgHdr1)
  int bPurgeable;                // 当前PCache1是不是可换入换出  
  unsigned int nMin;                 // 需要保留的缓存页个数
  unsigned int nMax;                  // PCache1最多可拥有的缓存页个数
  unsigned int n90pct;               // nMax * 0.9, pin页个数不能超过n90pct,否则失败
  unsigned int iMaxKey;              // 缓存页的最大页号
  unsigned int nPurgeableDummy;      // UNUSED
  unsigned int nRecyclable;          // LRU链表缓存页的个数
  unsigned int nPage;                 // 当前缓存页的个数
  unsigned int nHash;                // hash slot个数
  PgHdr1 **apHash;                  // hash表地址 
  PgHdr1 *pFree;                   // 空闲页链表
  void *pBulk;  						// PCache1管理的大块内存,内存布局:Page | PageHdr1 | extra | Page | PageHdr1 | extra ...
};

PgFreeslot

缓存页管理结构的空闲页链表

struct PgFreeslot {
  PgFreeslot *pNext; // 下一跳
};

PCacheGlobal

全局页缓存管理结构体

static SQLITE_WSD struct PCacheGlobal {
  PGroup grp;                    /* The global PGroup for mode (2) */
  int isInit;                    /* True if initialized */
  int separateCache;    // 
  int nInitPage;                 /* Initial bulk allocation size */   
  int szSlot;                    // 缓存行大小
  int nSlot;                     // 缓存行个数
  int nReserve;                 // 保留有空闲页的个数
  void *pStart, *pEnd;          // 全局缓存行的地址边界
  sqlite3_mutex *mutex;    // 保护下面三个字段的锁
  PgFreeslot *pFree;        // 全局空闲页链表的首页
  int nFreeSlot;                // 全局空闲页空闲slot的个数
  int bUnderPressure;     // 当nFreeSlot < nReserve 时 为 1,改变bUnderPressure需要加锁,读不需要加锁,因为读错并不会产生实际的损害
} pcache1_g;

PCacheGlobal 维护了一个空闲页链表,pFree字段就是页链表的首页,PgFreeslot 并没有申请单独的空间,而是复用PgHdr1的空间,当页归还到PCacheGlobal页链表时,将会对PgHdr1的空间重新初始化为PgFreeslot 并赋值。

接口

pcache1Init

int pcache1Init(void *NotUsed);

作用:初始化PCacheGlobal ,在Sqlite启动时初始化。

参数含义
NotUsed未使用参数

sqlite3PCacheBufferSetup

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

作用:设置PCacheGlobal的缓存页,在Sqlite启动时调用。

参数含义
pBufPCacheGlobal 缓存行地址
sz缓存行大小
n缓存行个数

pcache1Create

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

作用:创建一个PCache1

参数含义
szPagePCache1的页内容缓存大小
szExtra管理页缓存需要的额外内存大小(不包括PCache1需要使用的)
bPurgeable是否支持可回收机制

pcache1Fetch

sqlite3_pcache_page *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag);

作用:从pCache1中获取缓存页

参数含义
ppCache1指针
iKey页号
createFlagcreateFlag = 0时,不申请新页,PCache1有这个缓存页就返回,没有就返回NULL
createFlag = 1,PCache1没有这个缓存页且缓存页充足时,申请一个
createFlag = 1,PCache1没有这个缓存页时,即使缓存页不足时,也要申请一个

pcache1Unpin

void pcache1Unpin(sqlite3_pcache *p, sqlite3_pcache_page *pPg, int reuseUnlikely)

作用:解除PIN页,页缓存加入到LRU链表

参数含义
ppCache1指针
sqlite3_pcache_page页缓存句柄
reuseUnlikelytrue 直接归还到空闲页链表
false 加入到LRU链表

pcache1Destroy

void pcache1Destroy(sqlite3_pcache *p);

作用:销毁PCache1

参数含义
ppCache1指针
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值