当前位置: 首页 > news >正文

网站流量成本360上网安全导航

网站流量成本,360上网安全导航,python手机版,今日山西省最新消息内存管理的意义:内存是系统中重要的基本资源之一,内存的管理是指其分配、使用和回收的管理;保障各个程序内存的正常分配和回收。 虽然操作系统以及提供了一套内存管理的函数,但是PHP还是自己实现了一套内存管理方案-PHP内存管理器…

内存管理的意义:内存是系统中重要的基本资源之一,内存的管理是指其分配、使用和回收的管理;保障各个程序内存的正常分配和回收。

虽然操作系统以及提供了一套内存管理的函数,但是PHP还是自己实现了一套内存管理方案-PHP内存管理器(Zend Memory Manager简称MM)如下图:

PHP7内存管理器示意图

PHP7内存管理器示意图

从图中可以看出PHP脚本运行所需内存不是直接从系统调用的,而是先通过内存管理器提供的一系列API接口(zend-mm-alloc-small、alloc-large、alloc-huge等,alloc意思为分配,huge为超大)申请:如果MM中有足够的内存,则直接分配给脚本;如果MM中不够用,则MM再向系统申请。这样可以有效减少PHP向系统调用的次数,并且优化内存空间使用效率。因为C、C++需要手动申请和释放内存,所以其比PHP开发要难。

在此引入一个内存池的概念:提供了一个更有效率的解决方案,即预先规划一定数量的内存区块,使得整个程序可以在运行期规划(allocate)、使用(access)、归还(free)内存区块。一个池子无非就是先占用一块内存,然后给需要的人使用。


内存管理准备知识

据PHP 7核心开发者描述,PHP 7在内存管理上的CPU时间节省达到了21%,提升巨大。

PH7其实是借鉴了前辈的内存管理方案:jemalloc和tcmalloc,这两个分别是火狐和chrome两大浏览器的内存管理器。这种内存管理器的内存分配思想大致就是:先申请一大块内存,自己先占着,然后再按照大中小三种规格分割成小块,放在内存池中。当程序申请内存时,MM从池子中挑选合适大小的内存给程序。

基本概念

PHP7内存管理器的的代码是在php-7.x.x/Zend/zenc_alloc.c中实现的。它维护了三种规格的内存,分别是chunk、page、slot;

这三种大小是在php-7.x.x/Zend/zenc_alloc_sizes.h中定义的:

#define ZEND_MM_CHUNK_SIZE (2 * 1024 * 1024)               /* 2 MB  */
#define ZEND_MM_PAGE_SIZE  (4 * 1024)                      /* 4 KB  */
#define ZEND_MM_PAGES      (ZEND_MM_CHUNK_SIZE / ZEND_MM_PAGE_SIZE)  /* 512 */

page是在chunk中分配的,那么一个chunk可以分为2MB/4KB=512个page,如图2所示。

图2 chunk和page示意图

图2 chunk和page示意图

在PHP 7中,对于chunk大块内存的申请是使用mmap函数实现的,其中mmap函数原型如下:

/* MAP_FIXED leads to discarding of the old mapping, so it can't be used. */
void *ptr = mmap(addr, size, PROT_READ | PROT_WRITE, flags /*| MAP_POPULATE | MAP_HUGETLB*/, ZEND_MM_FD, 0);//PHP7中对应的调用如下
ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_HUGETLB, -1, 0);

各个参数的含义如下:

  1. start:映射区开始地址,0表示由系统决定的起始地址,PHP7传入的NULL,也就是0

  2. length:映射区长度,以字节为单位,不足一页时按一页处理

  3. prot

    :期望的内存保护标志不能与文件的打开方式冲突。prot可以是以下的某个值,且可以使用or将合理的组合在一起:

    1. PROT_EXEC:页内容可执行
    2. PROT_READ:页内容可读取
    3. PROT_WRITE:页可以写入
    4. PROT_NONE:页不可访问

PHP7中的为PROT_READ | PROT_WRITE,即可读写

  1. flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个位的组合体,PHP 7使用的是MAP_PRIVATE | MAP_ANON,前者是建立一个写入时复制的私有映射,后者表示匿名映射,映射区不与任何文件关联。
  2. fd:有效的文件描述词。PHP 7中设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。
  3. off_toffset:被映射对象内容的起点,PHP 7中设置为0。

PHP 7通过调用mmap函数,返回一大块内存,一般是chunk大小的倍数,后面的内存管理工作在这一大块内存上进行操作。

PHP 7的MM将申请内存按大小分成了3类:small内存、large内存、huge内存。

  1. small内存:小于等于3KB的内存。
  2. large内存:大于3KB且小于等于(2MB-4KB)的内存,可以对应整数倍的page,之所以要减掉4KB一个page的大小,后面会详细展开。
  3. huge内存:大于2MB-4KB的内存,可以直接对应整数倍的chunk。

与mmap相反的操作是int munmap(void *start, size_t length),用来取消参数start所指的映射内存起始地址,参数length则是欲取消的内存大小,该函数在释放内存的时候使用。

内存对齐

在用C/C++进行软件开发、申请内存时,编译器可以帮我们实现内存对齐,虽然看上去浪费了内存,但是提升了CPU访问内存的速度。

对齐举例:在PHP 7的内存池管理中,比如我们申请300B的内存,如果以256B对齐,则对齐后的内存应该是512B(256的2倍)。

PHP7中的内存对齐主要用到一下三个宏

//还是在zend_alloc.c中
#define ZEND_MM_ALIGNED_OFFSET(size, alignment) \(((size_t)(size)) & ((alignment) - 1))
#define ZEND_MM_ALIGNED_BASE(size, alignment) \(((size_t)(size)) & ~((alignment) - 1))
#define ZEND_MM_SIZE_TO_NUM(size, alignment) \(((size_t)(size) + ((alignment) - 1)) / (alignment))

如何理解这几个宏呢?下面举例来说明一下,假如要申请一个大小为4KB的内存,并以0x1000对齐,如图3所示。

图3 内存地址对齐示例

图3 内存地址对齐示例

  • 申请0x1000+0x1000-0x0001=0x1fff的内存(也就是多申请0xfff的内存),比如申请到的起始地址为0x103c60120,结束地址为0x103c6211f;因为此时的地址不是0x1000对齐的(因为0x103c60120不是0x1000的整数倍),所以要进行对齐操作。
  • 为了对齐,先释放0x103c60120到0x103c61000(恰好是起始地址和结束地址区间内0x1000的整数倍)的0xee0长度的内存,起始保证了起始地址为0x103c61000,是与0x1000对齐的。
  • 释放0x103c62000到0x103c6211f的0x11f长度内存(两次释放的内存长度0xee0+0x11f=0xfff,恰好为多申请的长度)。
  • 剩下的即为需要的0x1000长度,起始地址为0x103c61000,结束地址为0x103c62000的内存。

使用此内存时,比如有一内存地址为0x103c61120,通过宏计算,可以得出,此内存所在的page的起始地址为0x103c61000,在此page的偏移量为0x120,能够快速定位内存地址所在的page,提高效率。

以上是内存管理的概念和内存对齐方法


内存管理的数据结构

PHP7的内存管理用到了一些结构体,其中核心的结构体有zend_mm_heap、zend_mm_page、zend_mm_chunk。其中zend_mm_page最简单,对应的是4KB的char数组,下面对zend_mm_heap和zenc_mm_chunk进行讨论。

_zend_mm_heap

以下为_zend_mm_heap的结构体定义

struct _zend_mm_heap {
#if ZEND_MM_CUSTOMint                use_custom_heap;
#endif
#if ZEND_MM_STORAGEzend_mm_storage   *storage;
#endif
#if ZEND_MM_STATsize_t             size;                    /* current memory usage */size_t             peak;                    /* peak memory usage */
#endifzend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
#if ZEND_MM_STAT || ZEND_MM_LIMITsize_t             real_size;               /* current size of allocated pages */
#endif
#if ZEND_MM_STATsize_t             real_peak;               /* peak size of allocated pages */
#endif
#if ZEND_MM_LIMITsize_t             limit;                   /* memory limit */int                overflow;                /* memory overflow flag */
#endifzend_mm_huge_list *huge_list;               /* list of huge allocated blocks */zend_mm_chunk     *main_chunk;zend_mm_chunk     *cached_chunks;			/* list of unused chunks */int                chunks_count;			/* number of allocated chunks */int                peak_chunks_count;		/* peak number of allocated chunks for current request */int                cached_chunks_count;		/* number of cached chunks */double             avg_chunks_count;		/* average number of chunks allocated per request */int                last_chunks_delete_boundary; /* numer of chunks after last deletion */int                last_chunks_delete_count;    /* number of deletion over the last boundary */
#if ZEND_MM_CUSTOMunion {struct {void      *(*_malloc)(size_t);void       (*_free)(void*);void      *(*_realloc)(void*, size_t);} std;struct {void      *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);void       (*_free)(void*  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);void      *(*_realloc)(void*, size_t  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);} debug;} custom_heap;HashTable *tracked_allocs;
#endif
};

下面解释下变量的含义。

  1. size/real_size:size代表的是MM当前申请的已使用的内存,real_size还包括申请的未使用的内存;可以通过PHP的函数memory_get_usage来获取,其PHP函数原型如下:

    int memory_get_usage([bool $real_usage = false])

    $real_usage默认为false,只返回使用的内存大小;对于true的情况,会返回包括没有使用的分配内存的大小。在PHP7的源码中有对应的实现:

    ZEND_API size_t zend_memory_usage(int real_usage)
    {
    #if ZEND_MM_STATif (real_usage) {return AG(mm_heap)->real_size;} else {size_t usage = AG(mm_heap)->size;return usage;}
    #endifreturn 0;
    }
    

    从源码中可以看出参数为true时,返回的是real_size;当为false时,返回的是size;size和real_size会在申请和释放内存时进行修改。

  2. peak/real_peak:peak是emalloc上报的内存峰值,可以通过PHP的函数memory_get_peak_usage来获取,其PHP函数的原型如下:

    int memory_get_peak_usage([bool $real_usage = false])

    $real_usage默认为false,只返回emalloc上报的内存峰值大小;对于true的情况,会返回内存分配峰值的大小;在PHP7的源码中,有对应的实现:

    ZEND_API size_t zend_memory_peak_usage(int real_usage){#if ZEND_MM_STATif (real_usage) {return AG(mm_heap)->real_peak;} else {return AG(mm_heap)->peak;}#endifreturn 0;}
    

    从源码中,可以看出true时返回的是real_peak,同样,在申请和释放内存时real_peak和peak也会进行修改。

  3. free_slot:指针数组,存储30种规格的small内存链表的首地址

  4. limit:存储在MM可申请内存的最大值,MM每当向系统申请chunk或huge的内存时,会判断申请后的内存值是否大于limit,如果大于,则进行垃圾回收。该参数可以通过php.ini中的memory_limit配置。

  5. overflow:当申请的内存总数超出MM的limit时,先进行垃圾回收,如果回收失败,则判断overflow是否为1,如果是1则抛出异常,中断进程(PHP项目中经常遇到的allowed memory size of ** byte exhausted tried to allocate ** bytes就是这样跑出来的)

  6. main_chunk:双向链表,存储使用中的chunk的首地址

  7. cached_chunks:双向链表,缓存的chunk的首地址

  8. chunks_count:使用中的chunk个数,也就是链表main_chunk中的元素个数。

  9. peak_chunks_count:此次http请求中申请的chunk个数最大值,初始化为1,且每次请求开始都会重置为1

  10. cached_chunks_count:缓存中的chunk个数,也就是链表cached_chunks中的元素个数

  11. avg_chunks_count:历次请求使用chunk的个数平均值,初始值为1.0,每次请求结束时,会重新计算此值,置为avg_chunks_count和peak_chunks_count的平均值。

    对于chunk相关的变量,会在后续chunk章节详细展开

  12. huge_list:用以挂载分配的大块内存的单向列表,方便后续MM关闭时释放。

结构体_zend_mm_heap本身是要占内存的,也保存在内存管理申请的内存中。

_zend_mm_heap中有一个非常重要的结构——_zend_mm_chunk,下面讨论一下这个结构体。

_zend_mm_chunk

PHP 7的MM是一个多级内存分配器——预先定义内存块级别,按需要分配空间的大小找到对应级别,对齐分配。前文提到,chunk大小为2MB;每个chunk可以切割为512个page,一个page是4KB。在chunk内部,以page为单位进行管理。参考以下宏:

#define ZEND_MM_CHUNK_SIZE (2 * 1024 * 1024)               /* 2 MB  */
#define ZEND_MM_PAGE_SIZE  (4 * 1024)                      /* 4 KB  */
#define ZEND_MM_PAGES      (ZEND_MM_CHUNK_SIZE / ZEND_MM_PAGE_SIZE)  /* 512 */

一个chunk大小为2MB, MM管理chunk的变量,使用的是结构体_zend_mm_chunk:

struct _zend_mm_chunk {zend_mm_heap      *heap;zend_mm_chunk     *next;zend_mm_chunk     *prev;uint32_t           free_pages;				/* number of free pages */uint32_t           free_tail;               /* number of free pages at the end of chunk */uint32_t           num;char               reserve[64 - (sizeof(void*) * 3 + sizeof(uint32_t) * 3)];zend_mm_heap       heap_slot;               /* used only in main chunk */zend_mm_page_map   free_map;                /* 512 bits or 64 bytes */zend_mm_page_info  map[ZEND_MM_PAGES];      /* 2 KB = 512 * 4 */
};

各变量的含义如下。

  1. heap:zend_mm_heap类型的指针,对应的是9.3.1节中AG里面的mm_heap的地址。
  2. next:zend_mm_chunk类型的指针,指向下一个chunk。
  3. prev:zend_mm_chunk类型的指针,指向上一个chunk。由next/prev可见zend_mm_chunk是双向链表。
  4. free_pages:此chunk中可用的page个数,如图9-5所示,此chunk一共使用了9个page,则free_pages为512-9=503。

PHP7page使用情况分析

PHP7page使用情况分析

  1. free_tail:此chunk的最后一块连续可用page的起始编号,主要用于快速查找连续可用page,此值并不准确,但不影响最后结果,如图9-5所示,free_tail应该为363。
  2. free_map:在64位机器下,其为8个元素的数组,每个元素为64bit的整型,所以一共有8×64bit=512bit,对应512个page。已使用的page,对应的bit置为1,灰色部分;未使用(可用)的page,对应的bit置为0,白色部分,如图所示。

free_map对应的512bit

free_map对应的512bit

  1. map:512个元素的数组,每个元素为一个32bit的整型,用来记录每个page的使用情况,比较复杂,如图所示。
    PHP7内存管理large内存的map使用情况示例s

    PHP7内存管理large内存的map使用情况示例s

    高位的2个bit,用于标记此page的使用类型,有4种情况:0x0、0x1、0x2、0x3,其中0x0代表此page未使用,0x1代表此page用于large内存,0x2和0x3均代表此page用于small内存。当此page用于large内存时,如果低位的10个bit为0,则代表此page被其前面且连续的page一起用于一次申请的内存;如果非0,假定值为page_count,则代表此page开始的连续page_count个page一起用于一次申请的内存,比如图9-6中一次申请了3个连续的page,起始编号为360,那么map[360]、map[361]、map[362]的低10位分别为3、0、0。

    注意free_map是8× 8B,也就是8× 8× 8=512bit,这512个bit对应512个page,每个bit只能取0或者1,代表对应page的使用情况。而map是512个uint32_t,也就是512× 4B,每一个uint32_t代表一个page的使用情况。

  2. num:代表此chunk在链表main_chunk中的编号,很明显,当申请第一个chunk时,num为0。对于非第一个chunk, num的值为在前一个chunk的num上加1。

  3. reserve:保留字段,在C语言开发中的结构体中尤为常见,用于结构体版本升级之类。10)heap_slot:在MM进行初始化时,会创建第一个chunk,而第一个chunk的此字段,才有意义。其实全局指针alloc_globals.mm_heap指向的便是第一个chunk的heap_slot。

每申请一个chunk,都需要对chunk进行初始化,大致流程如下所示。

  1. 将此chunk放入环状双向链表main_chunk的最后面。

  2. 将free_pages置为512-1=511(第0个page被chunk的头信息占用)。

  3. 将free_tail置为1。

  4. 将num在上一个元素的计数基础上加1(chunk->prev->num+1)。

  5. 将free_map[0]标记为1,代表第0个被使用。

  6. 将map[0]标记为0x40000000 | 0x01,0x40000000代表第0个page使用large内存,0x01代表从第0个page起,连续1个page被使用。

    _zend_mm_chunk本身是要占用内存的,我们输出_zend_mm_chunk的size:

    (gdb) p sizeof(zend_mm_chunk) $3 = 2552

这个结构体占了2552B,它存放在chunk的第0个page上,如图所示。

内存管理chunk和page在MM中的位置

内存管理chunk和page在MM中的位置

当申请一个chunk时,MM先判断双向链表cached_chunks是否存在chunk,如果不存在,则直接向操作系统申请一个地址以2MB对齐的chunk,添加到main_chunk中,然后返回给申请者;如果cached_chunks中存在chunk,则讲头部的chunk摘除,然后添加chunk进行初始化,一个chunk被分成512个page,其中511个page可用,第0个page用于存放这个chunk的管理结构体struct_zend_mm_chunk。

释放一个chunk时,MM先将此chunk从main_chunk中移除,并将chunks_count减一。然后判断当前使用的chunk数是否小于历次请求使用的chunk个数平均值avg_chunks_count。如果小于,则将此chunk放入双向链表cached_chunks中;如果不小于,则直接向操作系统释放此块内存。

到此我们研究了AG里面mm_heap的结构,以及chunk和page结构和相互关系,有了这些准备后,再来看下PHP内存管理的详细实现。

PHP内存管理器初始化流程

PHP内存管理器初始化流程

PHP内存管理器初始化流程

内存分配的函数调用流程

可在php7.x.x/Zend/zend_alloc.c中搜索_emalloc追溯相关代码

PHP内存分配函数调用流程

PHP内存分配函数调用流程

内存释放的函数调用流程

ZEND_API void ZEND_FASTCALL _efree(void *ptr)
{zend_mm_free_heap(AG(mm_heap), ptr);
}static zend_always_inline void zend_mm_free_heap(zend_mm_heap *heap, void *ptr)
{//计算当前地址ptr相对于chunk的偏移size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE);//偏移为0,说明是huge内存,直接释放if (UNEXPECTED(page_offset == 0)) {if (ptr != NULL) {zend_mm_free_huge(heap, ptr);}} else {//计算chunk首地址zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE);//计算页号int page_num = (int)(page_offset / ZEND_MM_PAGE_SIZE);//获得页属性信息zend_mm_page_info info = chunk->map[page_num];//small内存if (EXPECTED(info & ZEND_MM_IS_SRUN)) {zend_mm_free_small(heap, ptr, ZEND_MM_SRUN_BIN_NUM(info));}//large内存else /* if (info & ZEND_MM_IS_LRUN) */ {int pages_count = ZEND_MM_LRUN_PAGES(info);//将页标记为空闲zend_mm_free_large(heap, chunk, page_num, pages_count);}}
}static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr, int bin_num)
{zend_mm_free_slot *p;//插入空闲链表头部即可p = (zend_mm_free_slot*)ptr;p->next_free_slot = heap->free_slot[bin_num];heap->free_slot[bin_num] = p;
}

内存释放函数调用关系

内存释放函数调用关系

PHP内存管理总结

1)需要明白一点:任何内存分配器都需要额外的数据结构来记录内存的分配情况;

2)内存池是代替直接调用malloc/free、new/delete进行内存管理的常用方法;内存池中空闲内存块组织为链表结果,申请内存只需要查找空闲链表即可,释放内存需要将内存块重新插入空闲链表;

3)PHP采用预分配内存策略,提前向操作系统分配2M字节大小内存,称为chunk;同时将内存分配请求根据字节大小分为small、huge、large三种;

4)small内存,采用“分离存储”思想;将空闲内存块按照字节大小组织为多个空闲链表;

5)large内存每次回分配连续若干个页,采用最佳适配算法;

6)huge内存直接使用mmap函数向操作系统申请内存(申请大小是2M字节整数倍);

7)chunk中的每个页只会被切割为相同规格的内存块;所以不需要再每个内存块添加头部,只需要记录每个页的属性即可;

8)如何方便根据地址计算当前内存块属于chunk中的哪一个页?PHP分配的chunk都是2M字节对齐的,任意地址的低21位即是相对chunk首地址,除以页大小则可获得页号;


未完待续

http://www.yidumall.com/news/87838.html

相关文章:

  • 东莞网站制作个性化网络公司seo推广
  • 重庆h5建站模板淘宝网站的推广与优化
  • 哪个网站专业做饲料南通百度seo代理
  • 网站建设找星火龙360搜索引擎推广
  • 网站建设资料填写北京网上推广
  • 福建省人民政府驻京办新乡seo外包
  • 松江区网站制作与推广泰州百度关键词优化
  • 沈阳疫情最新消息今天新增病例seo公司优化方案
  • wordpress 主题 微信seo优化需要多少钱
  • 网页设计策划书方案百度网站优化
  • 职业教育网站建设seo关键词排名如何
  • 聚美优品网站建设产品策略推广网站seo
  • wordpress 防ddos青岛seo建站
  • 电子政务建设网站图片关键词推广是什么意思
  • 淘客手机网站源码郑州seo优化顾问热狗
  • 电子政务政府网站建设方案seo网络科技有限公司
  • 手机培训网站建设seo优化网络公司
  • 网上销售怎样做网站2022年传销最新消息
  • 淘宝网站用什么语言做的seo排名软件有用吗
  • 荆州网站建设seo全称
  • 别人网站 自己的二级域名网页制作软件有哪些
  • 网站常用的中文字体seo网站分析工具
  • 法律顾问 网站 源码珠海网站建设优化
  • 做牛排的网站百度精简版入口
  • 建设银行住房公积网站花西子网络营销策划方案
  • 深圳品牌床垫西安seo推广公司
  • 大型网站开发视频链接式友谊
  • 做图专业软件下载网站商业推广软文范例
  • 团队如何分工做网站青岛seo优化
  • iis7部署网站免费seo关键词优化服务