天天看點

四十九、PHP核心探索:記憶體管理中的cache ☞ PHP将緩存添加到記憶體管理機制中

在維基百科中有這樣一段描述: 凡是位于速度相差較大的兩種硬體之間的,用于協調兩者資料傳輸速度差異的結構,均可稱之為Cache。 從最初始的處理器與記憶體間的Cache開始,都是為了讓資料通路的速度适應CPU的處理速度, 其基于的原理是記憶體中“程式執行與資料通路的局域性行為”。 同樣PHP記憶體管理中的緩存也是基于“程式執行與資料通路的局域性行為”的原理。 引入緩存,就是為了減少小塊記憶體塊的查詢次數,為最近通路的資料提供更快的通路方式。

PHP将緩存添加到記憶體管理機制中做了如下一些操作:

  • 辨別緩存和緩存的大小限制,即何時使用緩存,在某些情況下可以以最少的修改禁用掉緩存
  • 緩存的存儲結構,即緩存的存放位置、結構和存放的邏輯
  • 初始化緩存
  • 擷取緩存中内容
  • 寫入緩存
  • 釋放緩存或者清空緩存清單

首先我們看辨別緩存和緩存的大小限制,在PHP核心中,是否使用緩存的辨別是宏ZEND_MM_CACHE(Zend/zend_alloc.c 400行), 緩存的大小限制與size_t結構大小有關,假設size_t占4位,則預設情況下,PHP核心給PHP記憶體管理的限制是128K(32 * 4 * 1024)。 如下所示代碼:

#define ZEND_MM_NUM_BUCKETS (sizeof(size_t) << 3)
 
#define ZEND_MM_CACHE 1
#define ZEND_MM_CACHE_SIZE (ZEND_MM_NUM_BUCKETS * 4 * 1024)      
如果在某些應用下需要禁用緩存,則将ZEND_MM_CACHE宏設定為0,重新編譯PHP即可。 為了實作這個處修改所有地方都生效的功能,則在每個需要調用緩存的地方在編譯時都會判斷ZEND_MM_CACHE是否定義為1。

如果我們啟用了緩存,則在堆層結構中增加了兩個字段:

struct _zend_mm_heap {
 
#if ZEND_MM_CACHE
    unsigned int        cached; //  已緩存元素使用記憶體的總大小
    zend_mm_free_block *cache[ZEND_MM_NUM_BUCKETS]; //  存放被緩存的塊
#endif      

如上所示,cached表示已緩存元素使用記憶體的總大小,zend_mm_free_block結構的數組裝載被緩存的塊。 在初始化記憶體管理時,會調用zend_mm_init函數。在這個函數中,當緩存啟用時會初始化上面所說的兩個字段,如下所示:

#if ZEND_MM_CACHE
    heap->cached = 0;
    memset(heap->cache, 0, sizeof(heap->cache));
#endif      

程式會初始化已緩存元素的總大小為0,并給存放緩存塊的數組配置設定記憶體。 初始化之後,如果外部調用需要PHP核心配置設定記憶體,此時可能會調用緩存, 之是以是可能是因為它有一個前提條件,即所有的緩存都隻用于小于的記憶體塊的申請。 所謂小塊的記憶體塊是其真實大小小于ZEND_MM_MAX_SMALL_SIZE(272)的。 比如,在緩存啟用的情況下,我們申請一個100Byte的記憶體塊,則PHP核心會首先判斷其真實大小, 并進入小塊記憶體配置設定的流程,在此流程中程式會先判斷對應大小的塊索引是否存在,如果存在則直接從緩存中傳回, 否則繼續走正常的配置設定流程。

當使用者釋放記憶體塊空間時,程式最終會調用_zend_mm_free_int函數。在此函數中,如果啟用了緩存并且所釋放的是小塊記憶體, 并且已配置設定的緩存大小小于緩存限制大小時,程式會将釋放的塊放到緩存清單中。如下代碼:

#if ZEND_MM_CACHE
    if (EXPECTED(ZEND_MM_SMALL_SIZE(size)) && EXPECTED(heap->cached < ZEND_MM_CACHE_SIZE)) {
        size_t index = ZEND_MM_BUCKET_INDEX(size);
        zend_mm_free_block **cache = &heap->cache[index];
 
        ((zend_mm_free_block*)mm_block)->prev_free_block = *cache;
        *cache = (zend_mm_free_block*)mm_block;
        heap->cached += size;
        ZEND_MM_SET_MAGIC(mm_block, MEM_BLOCK_CACHED);
#if ZEND_MM_CACHE_STAT
        if (++heap->cache_stat[index].count > heap->cache_stat[index].max_count) {
            heap->cache_stat[index].max_count = heap->cache_stat[index].count;
        }
#endif
        return;
    }
#endif