在[維基百科](http://zh.wikipedia.org/wiki/Cache)中有這樣一段描述:**凡是位于速度相差較大的兩種硬件之間的,用于協調兩者數據傳輸速度差異的結構,均可稱之為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
當堆的內存溢出時,程序會調用zend_mm_free_cache釋放緩存中。整個釋放的過程是一個遍歷數組,對于每個數組的元素程序都遍歷其所在鏈表中在自己之前的元素,執行合并內存操作,減少堆結構中緩存計量數字。具體實現參見Zend/zend_alloc.c的909行。
在上面的一些零碎的代碼塊中我們有看到在ZEND_MM_CACHE宏出現時經常會出現ZEND_MM_CACHE_STAT宏。這個宏是標記是否啟用緩存統計功能,默認情況下為不啟用。緩存統計功能也有對應的存儲結構,在分配,釋放緩存中的值時,緩存統計功能都會有相應的實現。
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和Zend引擎
- 第二節 SAPI概述
- Apache模塊
- 嵌入式
- FastCGI
- 第三節 PHP腳本的執行
- 詞法分析和語法分析
- opcode
- opcode處理函數查找
- 第四節 小結
- 第三章 變量及數據類型
- 第一節 變量的結構和類型
- 哈希表(HashTable)
- PHP的哈希表實現
- 鏈表簡介
- 第二節 常量
- 第三節 預定義變量
- 第四節 靜態變量
- 第五節 類型提示的實現
- 第六節 變量的生命周期
- 變量的賦值和銷毀
- 變量的作用域
- global語句
- 第七節 數據類型轉換
- 第八節 小結
- 第四章 函數的實現
- 第一節 函數的內部結構
- 函數的內部結構
- 函數間的轉換
- 第二節 函數的定義,傳參及返回值
- 函數的定義
- 函數的參數
- 函數的返回值
- 第三節 函數的調用和執行
- 第四節 匿名函數及閉包
- 第五節 小結
- 第五章 類和面向對象
- 第一節 類的結構和實現
- 第二節 類的成員變量及方法
- 第三節 訪問控制的實現
- 第四節 類的繼承,多態及抽象類
- 第五節 魔術方法,延遲綁定及靜態成員
- 第六節 PHP保留類及特殊類
- 第七節 對象
- 第八節 命名空間
- 第九節 標準類
- 第十節 小結
- 第六章 內存管理
- 第一節 內存管理概述
- 第二節 PHP中的內存管理
- 第三節 內存使用:申請和銷毀
- 第四節 垃圾回收
- 新的垃圾回收
- 第五節 內存管理中的緩存
- 第六節 寫時復制(Copy On Write)
- 第七節 內存泄漏
- 第八節 小結
- 第七章 Zend虛擬機
- 第一節 Zend虛擬機概述
- 第二節 語法的實現
- 詞法解析
- 語法分析
- 實現自己的語法
- 第三節 中間代碼的執行
- 第四節 PHP代碼的加密解密
- 第五節 小結
- 第八章 線程安全
- 第二節 線程,進程和并發
- 第三節 PHP中的線程安全
- 第九章 錯誤和異常處理
- 第十章 輸出緩沖
- 第十六章 PHP語言特性的實現
- 第一節 循環語句
- foreach的實現
- 第二十章 怎么樣系列(how to)
- 附錄
- 附錄A PHP及Zend API
- 附錄B PHP的歷史
- 附錄C VLD擴展使用指南
- 附錄D 怎樣為PHP貢獻
- 附錄E phpt測試文件說明
- 附錄F PHP5.4新功能升級解析
- 附錄G:re2c中文手冊