您的位置:首页 > 编程语言 > PHP开发

PHP内核探索:内存管理中的cache

2011-06-09 00:00 1861 查看
在维基百科中有这样一段描述: 凡是位于速度相差较大的两种硬件之间的,用于协调两者数据传输速度差异的结构,均可称之为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 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宏。 这个宏是标记是否启用缓存统计功能,默认情况下为不启用。缓存统计功能也有对应的存储结构,在分配,释放缓存中的值时, 缓存统计功能都会有相应的实现。

延伸阅读

此文章所在专题列表如下:

PHP内核探索:从SAPI接口开始

PHP内核探索:一次请求的开始与结束

PHP内核探索:一次请求生命周期

PHP内核探索:单进程SAPI生命周期

PHP内核探索:多进程/线程的SAPI生命周期

PHP内核探索:Zend引擎

PHP内核探索:再次探讨SAPI

PHP内核探索:Apache模块介绍

PHP内核探索:通过mod_php5支持PHP

PHP内核探索:Apache运行与钩子函数

PHP内核探索:嵌入式PHP

PHP内核探索:PHP的FastCGI

PHP内核探索:如何执行PHP脚本

PHP内核探索:PHP脚本的执行细节

PHP内核探索:操作码OpCode

PHP内核探索:PHP里的opcode

PHP内核探索:解释器的执行过程

PHP内核探索:变量概述

PHP内核探索:变量存储与类型

PHP内核探索:PHP中的哈希表

PHP内核探索:理解Zend里的哈希表

PHP内核探索:PHP哈希算法设计

PHP内核探索:翻译一篇HashTables文章

PHP内核探索:哈希碰撞攻击是什么?

PHP内核探索:常量的实现

PHP内核探索:变量的存储

PHP内核探索:变量的类型

PHP内核探索:变量的值操作

PHP内核探索:变量的创建

PHP内核探索:预定义变量

PHP内核探索:变量的检索

PHP内核探索:变量的类型转换

PHP内核探索:弱类型变量的实现

PHP内核探索:静态变量的实现

PHP内核探索:变量类型提示

PHP内核探索:变量的生命周期

PHP内核探索:变量赋值与销毁

PHP内核探索:变量作用域

PHP内核探索:诡异的变量名

PHP内核探索:变量的value和type存储

PHP内核探索:全局变量Global

PHP内核探索:变量类型的转换

PHP内核探索:内存管理开篇

PHP内核探索:Zend内存管理器

PHP内核探索:PHP的内存管理

PHP内核探索:内存的申请与销毁

PHP内核探索:引用计数与写时复制

PHP内核探索:PHP5.3的垃圾回收机制

PHP内核探索:内存管理中的cache

PHP内核探索:写时复制COW机制

PHP内核探索:数组与链表

PHP内核探索:使用哈希表API

PHP内核探索:数组操作

PHP内核探索:数组源码分析

PHP内核探索:函数的分类

PHP内核探索:函数的内部结构

PHP内核探索:函数结构转换

PHP内核探索:定义函数的过程

PHP内核探索:函数的参数

PHP内核探索:zend_parse_parameters函数

PHP内核探索:函数返回值

PHP内核探索:形参return value

PHP内核探索:函数调用与执行

PHP内核探索:引用与函数执行

PHP内核探索:匿名函数及闭包

PHP内核探索:面向对象开篇

PHP内核探索:类的结构和实现

PHP内核探索:类的成员变量

PHP内核探索:类的成员方法

PHP内核探索:类的原型zend_class_entry

PHP内核探索:类的定义

PHP内核探索:访问控制

PHP内核探索:继承,多态与抽象类

PHP内核探索:魔术函数与延迟绑定

PHP内核探索:保留类与特殊类

PHP内核探索:对象

PHP内核探索:创建对象实例

PHP内核探索:对象属性读写

PHP内核探索:命名空间

PHP内核探索:定义接口

PHP内核探索:继承与实现接口

PHP内核探索:资源resource类型

PHP内核探索:Zend虚拟机

PHP内核探索:虚拟机的词法解析

PHP内核探索:虚拟机的语法分析

PHP内核探索:中间代码opcode的执行

PHP内核探索:代码的加密与解密

PHP内核探索:zend_execute的具体执行过程

PHP内核探索:变量的引用与计数规则

PHP内核探索:新垃圾回收机制说明
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: