之前我们讨论了内存的工作原理,也进行了一些性能相关的测试。因此,让我们从今天开始,看看实践中的一些应用程序。首先,我们从PHP开始。2015年,PHP7的发布可以说是在技术圈引起了不小的轰动,因为它的执行效率直接是PHP5的两倍。在PHP7的内存方面,你知道作者做了哪些优化吗?你能深刻理解作者优化思想的精髓吗?让我们先看看一些核心数据结构的改进。PHP7zval变化1、php5.3中的zval:typedefunsignedintzend_object_handle;typedefstruct_zend_object_value{zend_object_handle句柄;zend_object_handlers*handlers;}zend_object_value;typedefunion_zvalue_value{longlval/*longvalue*val;/*长值*val;值*/结构{字符*val;国际长度;海峡;哈希表*ht;/*哈希表值*/zend_object_valueobj;}zvalue_value;struct_zval_struct{/*变量信息*/zvalue_valuevalue;/*值*/zend_uintrefcount__gc;zend_uchar类型;/*活动类型*/zend_ucharis_ref__gc;};我们这里只讨论64位操作系统下的情况。_zval_struct结构体由四个成员组成,其中zvalue_value稍微复杂一点,是一个union。union中最长的成员是一个指针加上一个int,8+4=12个字节。但是默认会进行内存对齐,所以_zval_struct会占用16个字节。那么_zval_struct的总字节数=value(16)+refcount__gc(4)+type(1)+is_ref__gc(1)=占22字节。最后考虑内存对齐,实际占用24字节。(如果计算有点晕,有兴趣的同学可以写个简单的测试代码,用sizeof查一下)2.PHP7.2中的zvaltypedefstruct_zval_structzval;typedefunion_zend_value{zend_longlval;/*长值*/doubledval;/*双精度值*/zend_refcounted*counted;zend_string*str;zend_array*arr;zend_object*obj;*功能;结构{uint32_tw1;uint32_tw2;}ww;}zend_value;struct_zval_struct{zend_value值;类型信息;}u1;联合国ion{......}u2;};7.2中的_zval_struct结构由3个成员组成,其中zend_value看起来比较复杂,但实际上只是一个8字节的unionu1也是一个union,Occupancy是4字节。u2也是如此。所以_zval_struct实际上占用了16个字节。PHP7HashTable变化一、PHP5.3中的HashTable:typedefstruct_hashtable{uintnTableSize;uintnTableMask;uintnNumOfElements;//这里注意:wasteingulongnNextFreeElement;桶*pInternalPointer;/*用于元素遍历*/Bucket*pListHead;Bucket*pListTail;桶**arBuckets;dtor_func_tpDestructor;zend_bool持久;无符号字符nApplyCount;SectionuintnTableMask4bytesuintnNumOfElements4bytes,ulongnNextFreeElement8bytes注意会浪费前4个字节,因为nNextFreeElement的起始地址需要对齐Bucket*pInternalPointer8-byteBucket*pListHead8-byteBucket*pListTail8bytesBucket**arBuckets8bytesdtor_func_tpDestructor8byteszend_boolpersistent1byteunsignedcharnApplyCoun1bytezend_boolbApplyProtection1byte最终总字节数=4+4+4+4(??nNextFreeElement前四个字节会留空)+8+8+8+8+8+8+1+1+1=67字节。另外,结构本身必须对齐到8的整数倍,所以实际占用了72个字节。2、PHP7.2DefaultHashTable:typedefstruct_zend_arrayHashTable;struct_zend_array{zend_refcounted_hgc;union{struct{ZEND_ENDIAN_LOHI_4(zend_ucharflags,zend_ucharnApplyCount,zend_ucharnIteratorsCount,zend_ucharconsistency)}v;uint32_t标志;}你;uint32_tnTableMask;桶*arData;uint32_tnNumUsed;uint32_tnNumOfElements;uint32_tnTableSize;uint32_tnInternalPointer;zend_longnNextFreeElement;dtor_func_tpDestructor;};4字节uint32_t占4字节Bucket*指针占8字节uint32_tnNumUsed占4字节uint32_tnNumOfElements占4字节uint32_tnTableSize占4字节uint32_tnInternalPointer占4字节zend_longnNextFreeElement占8字节dtor_func_tpDestructor占8字节Totalbytesinsection=8+4+4+8+4+4+4+4+8+8=56占用5个字节6个字节,刚好达到内存对齐的状态,没有额外的浪费。另外,PHP源码中经常出现的Buckets也从72字节降到了32字节。这里就不看源码了。优化思路的本质我们已经看到了两个核心数据结构的结构变化。上面的优化是什么意思?以HashTable为例,貌似从72字节优化到了56字节。这个内存节省不是特别多,只有20%多!但是这中间其实隐藏着两个深层次的优化思路。首先,CPU向内存请求数据时,是以CacheLine为单位的,我们说过CacheLine的大小是64字节。反观HashTable,7.2中的56字节只需要CPU对内存进行一次cacheline-sizedburstIO,就足够了。5.3中的72bytes,虽然只比CacheLine大一点点,但是不好意思,必须进行两次burstIO。因此,在计算机中,72字节实际上是56字节的两倍性能提升!!第二,CPU的L1、L2、L3的容量固定为几十K或者几十M。假设所有的缓存都是HashTable,那么在缓存容量不变的情况下,可以有多少个HashTable在PHP7中被缓存的会翻倍,缓存命中率会大幅提升。要知道L1命中后只需要1ns多一点的时间,但是如果穿透到内存,可能要延迟40多纳秒,差了几十倍。所以PHP内核的作者大牛深谙CPU和内存的工作原理。表面上看起来只是节省了几个字节,但实际上却爆发了巨大的性能提升!!练内功:内存部分:1.带你了解内存对齐的底层原理2.内存随机访问比顺序访问慢,让你深刻理解内存IO流程3.从DDR到DDR4,内存核心频率基本一致没太大进步4.实测内存有顺序IO和随机IO的访问延迟差异5.揭穿内存厂商的“谎言”,实测内存带宽的实际表现6.NUMA架构下内存访问延迟的区别!7.PHP7内存性能优化精髓8.一个内存性能提升的项目实践9.挑战Redis单实例内存最大极限,“遭遇”NUMA陷阱!我的公众号是“练内功练功”。在这里,我不是简单地介绍技术理论,也不是只介绍实践经验。而是理论联系实际,用实践加深对理论的理解,用理论提高技术实践能力。欢迎关注我的公众号,分享给你的朋友吧~~~
