1.概述
SQLite数据库连接会进行许多小的、短期的内存分配。当用sqlite3_prepare_v2()编译SQL语句时这种情况最常见。这些小的内存分配用来存储诸如表名和列名,解析树节点、单独的查询结果、B-Tree游标对象。这会导致频繁地调用malloc()和free(),用掉分配给SQLite的大部分CPU时间片。
SQLite引入lookaside来帮助减小分配内存的负载。在lookaside中,每个数据先预先分配一大块内存(通常50KB到100KB),然后把内存块分割成50到200个字节固定大小的多个slot,从而变成后备内存池。数据库连接的小内存分配使用lookaside的一个slot,而大内存分配则继续使用通用内存分配器。当后备池全部用完时也会转用通用内存分配器,不过在很多情况下后备池对小内存分配的使用是足够的。
因为lookaside分配总是同样的大小,因此分配和释放算法速度非常快,无需合并相邻空闲槽或查找大小合适的槽。每个数据库连接维护一个空闲槽的单链表。分配时直接摘下链表中的第一个内存槽,释放时直接把内存槽添加到链表的头部。此外,每个数据库连接已经运行在单个线程下(已经放置了互斥锁来强制做到这一点),无需额外的互斥锁来串行化后备槽列表的访问。因此,lookaside的分配和释放是非常快速的,在Linux和Max OS X工作站上进行速度测试,显示根据配置的lookaside,SQLite整个性能可以提高10%到15%。
2源码分析
可以使用如下接口来配置lookaside中slot的个数和大小:
sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, cnt);
其中sz为每个slot的字节数,缺省为1200字节。cnt是每个数据库连接的slot总个数,缺省值为100个slot。显然每个数据库连接的lookaside大小为sz*cnt字节,缺省为120KB。
默认配置由以下宏定义来决定:
define SQLITE_DEFAULT_LOOKASIDE 1200,100
一个单独数据库连接的后备池可以更改,使用下面的调用:
其中pBuf指向后备内存池空间。如果pBuf为NULL,SQLite将使用sqlite3_malloc()来获取需要的内存池空间。sz和cnt是每个后备槽的大小和槽的总数。如果pBuf不是NULL,则必须指向至少sz*cnt字节的空间。
数据库连接上的后备内存池配置只有在内存还没分配出去的情况下才能更改。因此,配置的设置应该在用sqlite3_open_v2()创建数据库连接后,而在执行任何SQL语句前立刻进行,如下的代码保证了这点:
if( db->lookaside.nOut ){
return SQLITE_BUSY;
}
这些slot通过链表连接在一起,从第1个slot开始,每个slot插入到链表的头部,如下图显示
每个slot用LookasideSlot指针来表示:
structLookasideSlot {
LookasideSlot *pNext; /* Next buffer in the list of free buffers */
};
在setupLookaside()函数中初始化,代码如下:
static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){
#ifndef SQLITE_OMIT_LOOKASIDE
void *pStart;
if( db->lookaside.nOut ){
return SQLITE_BUSY;
}
/* Free any existing lookaside buffer for this handle before
** allocating a new one so we don't have to have space for
** both at the same time.
*/
if( db->lookaside.bMalloced ){
sqlite3_free(db->lookaside.pStart);
}
/* The size of a lookaside slot after ROUNDDOWN8 needs to be larger
** than a pointer to be useful.
*/
sz = ROUNDDOWN8(sz); /* ((sz)&~7) */
if( sz<=(int)sizeof(LookasideSlot*) ) sz = 0;
if( cnt<0 ) cnt = 0;
if( sz==0 || cnt==0 ){
sz = 0;
pStart = 0;
}else if( pBuf==0 ){
sqlite3BeginBenignMalloc();
pStart = sqlite3Malloc( sz*cnt ); /* 分配sz*cnt大小的内存块 */
sqlite3EndBenignMalloc();
if( pStart ) cnt = sqlite3MallocSize(pStart)/sz;
}else{
pStart = pBuf;
}
db->lookaside.pStart = pStart;
db->lookaside.pFree = 0;
db->lookaside.sz = (u16)sz;
if( pStart ){
int i;
LookasideSlot *p;
assert( sz > (int)sizeof(LookasideSlot*) );
p = (LookasideSlot*)pStart;
for(i=cnt-1; i>=0; i--){
p->pNext = db->lookaside.pFree;//新插入的节点指向pFree
db->lookaside.pFree = p;// pFree指向最新插入的节点
p = (LookasideSlot*)&((u8*)p)[sz];//依次轮询每个slot
}
db->lookaside.pEnd = p;//内存块的末尾地址
db->lookaside.bDisable = 0;
db->lookaside.bMalloced = pBuf==0 ?1:0;
}else{
db->lookaside.pStart = db;
db->lookaside.pEnd = db;
db->lookaside.bDisable = 1;
db->lookaside.bMalloced = 0;
}
#endif /* SQLITE_OMIT_LOOKASIDE */
return SQLITE_OK;
}
每次分配时只要取出链表的第一个节点pFree即可,再把pFree往下移一个节点:
void *sqlite3DbMallocRawNN(sqlite3 *db,u64 n){
#ifndefSQLITE_OMIT_LOOKASIDE
LookasideSlot *pBuf;
assert( db!=0 );
assert( sqlite3_mutex_held(db->mutex) );
assert( db->pnBytesFreed==0 );
if( db->lookaside.bDisable==0 ){
//如果db->mallocFailed被置1,那么db->lookaside.bDisable>0
//见sqlite3OomFault()函数
assert( db->mallocFailed==0 );
if( n>db->lookaside.sz ){
db->lookaside.anStat[1]++;//分配的内存超过slot大小的状态+1
}elseif( (pBuf = db->lookaside.pFree)==0 ){
db->lookaside.anStat[2]++;//slot用完了的状态+1
}else{
db->lookaside.pFree = pBuf->pNext;// pFree下移
db->lookaside.nOut++;//已经使用的slot数量
db->lookaside.anStat[0]++;//正常分配的状态+1
if( db->lookaside.nOut>db->lookaside.mxOut ){
db->lookaside.mxOut = db->lookaside.nOut;
}
return (void*)pBuf;
}
}elseif( db->mallocFailed ){
return 0;
}
#else
assert( db!=0 );
assert( sqlite3_mutex_held(db->mutex) );
assert( db->pnBytesFreed==0 );
if( db->mallocFailed ){
return 0;
}
#endif
return dbMallocRawFinish(db, n);//如果分配的内存超过了slot的大小,或者slot分配完了,那么使用通用内存分配。
}
释放时先判断是不是lookaside分配的slot(即p在pStart和pEnd之间),如果是那么直接让p指向pFree,再把pFree更新为p,如果不是lookaside分配的,那么调用sqlite3_free(p)释放,代码如下:
if( isLookaside(db, p) ){
LookasideSlot *pBuf = (LookasideSlot*)p;
#ifdef SQLITE_DEBUG
/* Trash all content in the buffer being freed */
memset(p, 0xaa, db->lookaside.sz);
#endif
pBuf->pNext = db->lookaside.pFree;
db->lookaside.pFree = pBuf;
db->lookaside.nOut--;//分配的slot个数减1
return;
}