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

制作精美网站建设独立800元做小程序网站

制作精美网站建设独立,800元做小程序网站,珠海网站建设策划方案,网站以什么名字备案内存管理的意义:内存是系统中重要的基本资源之一,内存的管理是指其分配、使用和回收的管理;保障各个程序内存的正常分配和回收。 虽然操作系统以及提供了一套内存管理的函数,但是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.shuangfujiaoyu.com/news/38139.html

相关文章:

  • 深圳品牌策划培训青海网站seo
  • 网站建设海报图片潮州seo建站
  • 北京智能网站建设系统加盟网络营销课程速成班
  • 网站建设找美橙互联打开百度app
  • 网页搜索打不开网页长沙网站seo技术厂家
  • win7网站开发教程中国网民博客 seo
  • 北京做网站的公司哪家好软文写作公司
  • 重庆市建设工程信息网官网安全监督信息网湖南长沙seo
  • 网站可信认证必须做吗营销型网站建设步骤
  • 东风多利卡道路清障车做网站直通车关键词怎么选 选几个
  • 90后做网站餐饮营销方案
  • 医药类网站怎么做seo百度竞价广告怎么投放
  • 乐东黎族自治县住房建设局网站重庆网站排名提升
  • 邛崃做网站网页设计学生作业模板
  • jsp页面如何做网站pv统计营销型网站建设公司价格
  • 淘宝做的网站可靠吗自动点击器怎么用
  • b2b网站方案郑州网站关键词优化公司
  • 在阿里巴巴做网站多少钱2019百度指数里的资讯指数是什么
  • 做网站和网站维护需要多少钱谷歌搜索引擎入口2023
  • 国内公司网站需要备案吗全网营销整合营销
  • 网站seo方法baidu优化
  • 自己电脑上做网站seo分析seo诊断
  • 班级网站制作模板湖南seo优化哪家好
  • 宁波网络推广运营公司电话优化器
  • 做的好的网站网球排名即时最新排名
  • 普洱做网站的报价百度seo自动优化
  • 建站专业的推广服务平台现在百度推广有用吗
  • 廊坊网站建设费用营销型网站建设易网拓
  • 网站百度权重怎么提升郑州seo哪家专业
  • zencart网站地图生成郑州网络推广哪家口碑好