• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • SQLite3源碼學習(21) pcache1分析

    標簽: sqlite3  page cache  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大小的空間

    版權聲明:本文為pfysw原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/pfysw/article/details/79800422

    智能推薦

    Dagger2分析學習

    Dagger學習筆記 1.概要 Dagger是為Android和Java平臺提供的一個完全靜態的,在編譯時進行依賴注入的框架,原來是由Square公司維護的然后現在把這堆東西扔給 Google維護了 控制反轉(Inversion of Control,縮寫Ioc),是面向對象編程中的一種設計原則,可以用來減少計算機代碼之間的耦合度,其中最常見的方式是依賴注入 (Dependency Injecti...

    PAT乙級 1074 宇宙無敵加法器 (20分)【外加測試點1、3、5分析】

    地球人習慣使用十進制數,并且默認一個數字的每一位都是十進制的。而在 PAT 星人開掛的世界里,每個數字的每一位都是不同進制的,這種神奇的數字稱為“PAT數”。每個 PAT 星人都必須熟記各位數字的進制表,例如“……0527”就表示最低位是 7 進制數、第 2 位是 2 進制數、第 3 位是 5 進制數、第 4 位是 10 ...

    SQLite3源碼學習(28) Pager模塊之事務管理

           事務管理是Pager模塊中最核心的要素,所有對數據庫數據的讀寫操作都在事務中進行。一個數據庫需要在多線程的使用環境下保持數據的一致性,雖然操作系統對磁盤的讀寫操作并不是原子,但是通過事務以及日志的回滾機制使每一個事務的執行都是原子的,所以即使出現系統崩潰或斷電,數據庫并不會因此而損壞。        在一...

    SQLite3源碼學習(25) Pager模塊之事務鎖的實現2

    在上一篇文章中介紹了SQLite怎么用Linux中的記錄鎖來實現每一種類型的事務鎖。但這只適合多進程間的互斥,不適合多線程,在Linux中每一個進程只能擁有一把鎖,也就是說一個進程里的多個線程共用一把鎖,這時會出現一個線程擁有共享鎖,另一個線程再獲取獨占鎖時并不會出現排斥,僅僅是把當前進程的鎖改為獨占鎖,還有當一個線程占有鎖的時候可能被另一個線程釋放等等,這就破壞了數據庫中多事務的隔離性。所以SQ...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    猜你喜歡

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    精品国产乱码久久久久久蜜桃不卡