搜索
首页后端开发php教程PHP原理之内存管理中难懂的几个点_PHP教程

PHP的内存管理, 分为俩大部分, 第一部分是PHP自身的内存管理, 这部分主要的内容就是引用计数, 写时复制, 等等面向应用的层面的管理. 而第二部分就是今天我要介绍的, zend_alloc中描写的关于PHP自身的内存管理, 包括它是如何管理可用内存, 如何分配内存等.

另外, 为什么要写这个呢, 因为之前并没有任何资料来介绍PHP内存管理中使用的策略, 数据结构, 或者算法. 而在我们平时开发扩展, 修复PHP的bug的时候, 却对这一部分的知识需要有一个良好的理解. PHP开发组内的很多朋友也对这块不是很清楚, 所以我觉得有必要专门写一下.

一些基本的概念, 我就不赘述了, 因为看代码很容易能看懂, 我这里就主要介绍几个看代码没那么容易看懂的点, 为什么这么说呢, 呵呵, 我在写文章之前, 查找了下已有的资料, 已避免重复功, 其中看到了TIPI项目对这部分的描述, 发现其中错误很多. 所以, 我想这部分就是看代码也没那么容易看懂的点

目前, 英文版的介绍也在撰写中: Zend MM

Zend Memory Manager, 以下简称Zend MM, 是PHP中内存管理的逻辑. 其中有一个关键数据结构: zend_mm_heap:

\

Zend MM把内存非为小块内存和大块内存俩种, 区别对待, 对于小块内存, 这部分是最最常用的, 所以追求高性能. 而对于大块内存, 则追求的是稳妥, 尽量避免内存浪费.

所以, 对于小块内存, PHP还引入了cache机制:

\

Zend MM 希望通过cache尽量做到, 一次定位就能查找分配.

而一个不容易看懂的点是free_buckets的申明:

Q: 为什么free_buckets数组的长度是ZEND_MM_NUMBER_BUCKET个?

A: 这是因为, PHP在这处使用了一个技巧, 用一个定长的数组来存储ZEND_MM_NUMBER_BUCKET个zend_mm_free_block, 如上图中红色框所示. 对于一个没有被使用的free_buckets的元素, 唯一有用的数据结构就是next_free_block和prev_free_block, 所以, 为了节省内存, PHP并没有分配ZEND_MM_NUMBER_BUCKET * sizeof(zend_mm_free_block)大小的内存, 而只是用了ZEND_MM_NUMBER_BUCKET * (sizeof(*next_free_block) + sizeof(*prev_free_block))大小的内存..

我们来看ZEND_MM_SMALL_FREE_BUCKET宏的定义:

<ol class="dp-xml">
<li class="alt"><span><span>#define ZEND_MM_SMALL_FREE_BUCKET(heap, index)   </span></span></li>
<li>
<span>    (zend_mm_free_block*) ((char*)&heap-</span><span class="tag">></span><span>free_buckets[index * 2] +   </span>
</li>
<li class="alt"><span>        sizeof(zend_mm_free_block*) * 2 -   </span></li>
<li><span>        sizeof(zend_mm_small_free_block)) </span></li>
</ol>

之后, Zend MM 保证只会使用prev和next俩个指针, 所以不会造成内存读错..

那么, 第二个不容易看懂的点, 就是PHP对large_free_buckets的管理, 先介绍分配(TIPI项目组对此部分的描述有些含糊不清):

<ol class="dp-xml"><li class="alt"><span><span>static zend_mm_free_block *zend_mm_search_large_block(zend_mm_heap *heap, size_t true_size) </span></span></li></ol>

large_free_buckets可以说是一个建树和双向列表的结合:

\

large_free_buckets使用一个宏来决定某个大小的内存, 落在什么index上:

<ol class="dp-c"><li class="alt"><span><span>#define ZEND_MM_LARGE_BUCKET_INDEX(S) zend_mm_high_bit(S) </span></span></li></ol>

zend_mm_high_bit获取true_size中最高位1的序号(zend_mm_high_bit), 对应的汇编指令是bsr(此处, TIPI项目错误的说明为: “这个hash函数用来计算size的位数,返回值为size二进码中1的个数-1″).

也就是说, 每一个在large_free_buckets中的元素, 都保持着指向一个大小为在对应index处为1的size的内存块的指针. 诶, 有点绕口, 举个例子:

比如对于large_free_buckets[2], 就只会保存, 大小在0b1000到0b1111大小的内存. 再比如: large_free_buckets[6], 就保存着大小为0b10000000到0b11111111大小的内存的指针.

这样, 再分配内存的时候, Zend MM就可以快速定位到最可能适合的区域来查找. 提高性能.

而, 每一个元素又同时是一个双向列表, 保持着同样size的内存块, 而左右孩子(child[0]和child[1])分别代表着键值0和1, 这个键值是指什么呢?

我们来举个例子, 比如我向PHP申请一个true_size为0b11010大小的内存, 经过一番步骤以后, 没有找到合适的内存, PHP进入了zend_mm_search_large_block的逻辑来在large_free_buckets中寻找合适的内存:

1. 首先, 计算true_size对应的index, 计算方法如之前描述的ZEND_MM_LARGE_BUCKET_INDEX

2. 然后在一个位图结构中, 判断是否存在一个大于true_size的可用内存已经存在于large_free_buckets, 如果不存在就返回:

<ol class="dp-c">
<li class="alt"><span><span>size_t bitmap = heap->large_free_bitmap >> index;  </span></span></li>
<li>
<span class="keyword">if</span><span> (bitmap == 0) {  </span>
</li>
<li class="alt">
<span>   </span><span class="keyword">return</span><span> NULL;  </span>
</li>
<li><span>} </span></li>
</ol>

3. 判断, free_buckets[index]是否存在可用的内存:

<ol class="dp-c"><li class="alt"><span><span class="keyword">if</span><span> (UNEXPECTED((bitmap & 1) != 0)) </span></span></li></ol>

4. 如果存在, 则从free_buckets[index]开始, 寻找最合适的内存, 步骤如下:

4.1. 从free_buckets[index]开始, 如果free_buckets[index]当前的内存大小和true_size相等, 则寻找结束, 成功返回.

4.2. 查看true_size对应index后(true_size child[1]下面继续寻找, 如果free_buckets[index]->child[1]不存在, 则跳出. 如果true_size的当前最高位为0, 则在free_buckets[index]->child[0]下面继续寻找, 如果free_buckets[index]->child[0]不存在, 则在free_buckets[index]->child[1]下面寻找最小内存(因为此时可以保证, 在free_buckets[index]->child[1]下面的内存都是大于true_size的)

4.3. 出发点变更为2中所述的child, 左移一位ture_size.

5. 如果上述逻辑并没有找到合适的内存, 则寻找最小的”大块内存”:

<ol class="dp-c">
<li class="alt"><span><span class="comment">/* Search for smallest "large" free block */</span><span> </span></span></li>
<li><span>  best_fit = p = heap->large_free_buckets[index + zend_mm_low_bit(bitmap)];  </span></li>
<li class="alt">
<span>  </span><span class="keyword">while</span><span> ((p = p->child[p->child[0] != NULL])) {  </span>
</li>
<li>
<span>      </span><span class="keyword">if</span><span> (ZEND_MM_FREE_BLOCK_SIZE(p) </span>
</li>
<li class="alt"><span>          best_fit = p;  </span></li>
<li><span>      }  </span></li>
<li class="alt"><span>  } </span></li>
</ol>

注意上面的逻辑, (p = p->child[p->child[0] != NULL]), PHP在尽量寻找最小的内存.

为什么说, large_free_buckets是个键树呢, 从上面的逻辑我们可以看出, PHP把一个size, 按照二进制的0,1做键, 把内存大小信息反应到了键树上, 方便了快速查找.

另外, 还有一个rest_buckets, 这个结构是个双向列表, 用来保存一些PHP分配后剩下的内存, 避免无意义的把剩余内存插入free_buckets带来的性能问题(此处, TIPI项目错误的描述为: “这是一个只有两个元素的数组。 而我们常用的插入和查找操作是针对第一个元素,即heap->rest_buckets[0]“).

作者: Laruence( )   原文地址:

www.bkjia.comtruehttp://www.bkjia.com/PHPjc/445706.htmlTechArticlePHP的内存管理, 分为俩大部分, 第一部分是PHP自身的内存管理, 这部分主要的内容就是引用计数, 写时复制, 等等面向应用的层面的管理. 而第...
声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
使用PHP发送电子邮件的最佳方法是什么?使用PHP发送电子邮件的最佳方法是什么?May 08, 2025 am 12:21 AM

ThebestapproachforsendingemailsinPHPisusingthePHPMailerlibraryduetoitsreliability,featurerichness,andeaseofuse.PHPMailersupportsSMTP,providesdetailederrorhandling,allowssendingHTMLandplaintextemails,supportsattachments,andenhancessecurity.Foroptimalu

PHP中依赖注入的最佳实践PHP中依赖注入的最佳实践May 08, 2025 am 12:21 AM

使用依赖注入(DI)的原因是它促进了代码的松耦合、可测试性和可维护性。1)使用构造函数注入依赖,2)避免使用服务定位器,3)利用依赖注入容器管理依赖,4)通过注入依赖提高测试性,5)避免过度注入依赖,6)考虑DI对性能的影响。

PHP性能调整技巧和技巧PHP性能调整技巧和技巧May 08, 2025 am 12:20 AM

phperformancetuningiscialbecapeitenhancesspeedandeffice,whatevitalforwebapplications.1)cachingwithapcureduccureducesdatabaseloadprovesrovesponsemetimes.2)优化

PHP电子邮件安全性:发送电子邮件的最佳实践PHP电子邮件安全性:发送电子邮件的最佳实践May 08, 2025 am 12:16 AM

ThebestpracticesforsendingemailssecurelyinPHPinclude:1)UsingsecureconfigurationswithSMTPandSTARTTLSencryption,2)Validatingandsanitizinginputstopreventinjectionattacks,3)EncryptingsensitivedatawithinemailsusingOpenSSL,4)Properlyhandlingemailheaderstoa

您如何优化PHP应用程序的性能?您如何优化PHP应用程序的性能?May 08, 2025 am 12:08 AM

TOOPTIMIZEPHPAPPLICITIONSFORPERSTORANCE,USECACHING,数据库imization,opcodecaching和SererverConfiguration.1)InlumentCachingWithApcutCutoredSatfetchTimes.2)优化的atabasesbasesebasesebasesbasesbasesbaysbysbyIndexing,BeallancingAndWriteExing

PHP中的依赖注入是什么?PHP中的依赖注入是什么?May 07, 2025 pm 03:09 PM

依赖性注射inphpisadesignpatternthatenhancesFlexibility,可检验性和ManiaginabilybyByByByByByExternalDependencEctenceScoupling.itallowsforloosecoupling,EasiererTestingThroughMocking,andModularDesign,andModularDesign,butquirscarecarefulscarefullsstructoringDovairing voavoidOverOver-Inje

最佳PHP性能优化技术最佳PHP性能优化技术May 07, 2025 pm 03:05 PM

PHP性能优化可以通过以下步骤实现:1)在脚本顶部使用require_once或include_once减少文件加载次数;2)使用预处理语句和批处理减少数据库查询次数;3)配置OPcache进行opcode缓存;4)启用并配置PHP-FPM优化进程管理;5)使用CDN分发静态资源;6)使用Xdebug或Blackfire进行代码性能分析;7)选择高效的数据结构如数组;8)编写模块化代码以优化执行。

PHP性能优化:使用OpCode缓存PHP性能优化:使用OpCode缓存May 07, 2025 pm 02:49 PM

opcodecachingsimplovesphperforvesphpermance bycachingCompiledCode,reducingServerLoadAndResponSetimes.1)itstorescompiledphpcodeinmemory,bypassingparsingparsingparsingandcompiling.2)useopcachebachebachebachebachebachebachebysettingparametersinphametersinphp.ini,likeememeryconmorysmorysmeryplement.33)

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器