作者:李德小内存分配计算bin_numPHP源码中有一段小内存大小计算,具体在Zend/zend_alloc的zend_mm_small_size_to_bin函数中。c、它的用途是传入一个尺寸,计算出对应的规格。参见代码:if(size<=64){/*我们需要支持size==0...*/return(size-!!size)>>3;}else{t1=size-1;t2=zend_mm_small_size_to_bit(t1)-3;t1=t1>>t2;t2=t2-3;t2=t2<<2;return(int)(t1+t2);}可以看出这段代码讨论有两种情况:1、size小于等于64的情况;2.大小大于64的情况;我们将在下面详细分析这两个案例。对于size小于等于64的情况,看ZEND_MM_BINS_INFO宏可知,当size小于等于64时,是一个等差数列,自增8,所以直接用size除以8即可(源码右移3位)size>>3但是考虑到size等于8,16等,是(size-1)>>3那么0的情况一定是考虑到,所以源码中-1的处理是!!size,当size为0!!0=0。所以当size为0的时候,将-1转化为-0,最后就有了源码中的表达式(size-!!size)>>3对于size大于64的情况,t1=size-1;t2=zend_mm_small_size_to_bit(t1)-3;t1=t1>>t2;t2=t2-3;t2=t2<<2;返回(int)(t1+t2);初步困惑乍一看这段代码,很容易一头雾水,这t1和t2是什么?不过别怕,我们一步步分析/*num,size,count,pages*/#defineZEND_MM_BINS_INFO(_,x,y)\_(0,8,512,1,x,y)\_(1,16,256,1,x,y)\_(2,24,170,1,x,y)\_(3,32,128,1,x,y)\_(4,40,102,1,x,y)\_(5,48,85,1,x,y)\_(6,56,73,1,x,y)\_(7,64,64,1,x,y)\_(8,80,51,1,x,y)\_(9,96,42,1,x,y)\_(10,112,36,1,x,y)\_(11,128,32,1,x,y)\_(12,160,25,1,x,y)\_(13,192,21,1,x,y)\_(14,224,18,1,x,y)\_(15,256,16,1,x,y)\_(16,320,64,5,x,y)\_(17,384,32,3,x,y)\_(18,448,9,1,x,y)\_(19,512,8,1,x,y)\_(20,640,32,5,x,y)\_(21,768,16,3,x,y)\_(22,896,9,2,x,y)\_(23,1024,8,2,x,y)\_(24,1280,16,5,x,y)\_(25,1536,8,3,x,y)\_(26,1792,16,7,x,y)\_(27,2048,8,4,x,y)\_(28,2560,8,5,x,y)\_(29,3072,4,3,x,y)#endif/*ZEND_ALLOC_SIZES_H*/size=size-1;这是一个boundarycase,和之前一样,后面出现的size暂时认为减了一个,假设你不看这段源码,我们要在ZEND_MM_BINS_INFO中找到对应的bin_num。从ZEND_MM_BINS_INFO我们知道后面加4是一个组,分别是2^4,2^5,2^6...有了这个组信息,我们需要找到siez对应的bin_num,找到哪个组的size属于和组内size的偏移量是多少来计算组的起始位置。现在问题转化为上面三个小问题。让我们一一求解,找出大小属于哪一组最简单的方法就是比较大小,对吧?可以用if...else来一一比较,但是显然php源码并没有这样做,那我们还有什么方法呢?我们看不到十进制的东西,所以我们把这些值转换成二进制看看。64|100000080|101000096|1100000112|1110000128|01000000384|110000000448|111000000.....我们看上面的二进制,我们会发现每一组中的二进制长度是相等的,而且后面的每一个都比前面的多一位意味着我们可以计算二进制的长度来确定它的分组,那么二进制的长度是多少呢?其实就是当前二进制中最高位为1的位数。下面给出php源码对于1的位数的解决方法,这里就不分析了,只要知道它返回二进制intn=16中1的最高位即可;如果(大小<=0x00ff){n-=8;大小=大小<<8;}if(大小<=0x0fff){n-=4;大小=大小<<4;}如果(大小<=0x3fff){n-=2;尺寸=尺寸<<2;}if(size<=0x7fff){n-=1;}returnn;假设我们申请的size是65,那么这里的n返回7。计算size在group中的offset很简单,直接减去每个group的起始siezsize,然后除以当前group的差值(16,32,64...),即(size-64)/16(size-128)/32(size-256)/64现在我们来看一下上一步返回的值,每组都是7,8,9...,那我们看看如何计算组内的偏移量(size-2^4*4)/16=size/2^4-4(size-2^5*4)/32=size/2^5-4(size-2^6*4)/64=szie/2^6-47,8,9减去3是否可以得到4,5,6,这样我们就可以根据在哪个组的信息得到当前组(16、32、64...)的区别?当size为65时,offset是否等于(65-64)/2^4=0计算group的起始位置现在我们有了offset信息,假设我们的group是1,2,3,对不对?即最高位1减6得到分组信息。得到分组信息后,如何知道各组的起始位置呢?我们知道起始位置是8、12、16……也是一个等差数列是4n+4。我们正在查看size=65的示例。计算出的offset为0,计算的起始位置为4*1+4=8,所以当size=65的bin_num为起始位置加上Upperoffset8+0=8我们再看一个size=129的例子子偏移量是二进制的最高位1。位数为8,则8减3得到5(129-1-32*4)/64=0,计算的起始位置为4*2+4=12二两相加12+0=0size=193偏移量为二进制中1的最高位,位数为8(193-1-32*4)/64=2计算的起始位置为4*2+4=12两者相加为12+2=14size=1793偏移量为二进制的最高位1。位数为11(1793-1-256*4)/256=3计算的起始位置为4*5+4=24两者相加为24+3=27代码分析PHP实现代码1t1=大小-1;2t2=zend_mm_small_size_to_bit(t1)-3;3t1=t1>>t2;4t2=t2-3;5t2=t2<<2;6返回(int)(t1+t2);第一行t1=size-1;就是考虑64、128...这些边界条件的大小第二行t2=zend_mm_small_size_to_bit(t1)-3;这里调用了函数zend_mm_small_size_to_bit,我们来看这个函数/*highersetbitnumber(0->N/A,1->1,2->2,4->3,8->4,127->7,128->8等)*/intn=16;if(size<=0x00ff){n-=8;大小=大小<<8;}if(大小<=0x0fff){n-=4;大小=大小<<4;}如果(大小<=0x3fff){n-=2;大小=大小<<2;}如果(大小<=0x7fff){n-=1;}返回n;看注释我们知道,这个函数是用来返回当前size二进制中最高位1的个数。具体方法其实就是二分法。我们通过zend_mm_small_size_to_bit这个函数获取的是size二进制中最高位1的个数,那么这个-3有什么神奇的操作呢?上题分析中提到,我们计算组内size偏移量的计算公式(size-2^4*4)/16=size/2^4-4(size-2^5*4)/32=size/2^5-4(size-2^6*4)/64=szie/2^6-4这里通过运算得到的二进制位数为7,8,9...-3得到对应的4,5,6...第三行t1=t1>>t2;将t1右移t2位,这是什么神奇的操作?这里我们写出最终计算bin_num的数学公式,等于每组起始位置加上组内偏移量binnum=(4n+4)+(size/2^n-4)binnum=4n+size/2^n这样我们就知道第三行的意思了,就是把size向右移动2^n次到第四行t2=t2-3;这个很容易理解,可以参考上面得到每组的起点位置方法的第五行t2=t2<<2;我们看一下bin_num的计算公式binnum=(4n+4)+(size/2^n-4)binnum=4n+size/2^n那么这一行就很容易理解了,就是计算起始每组4n的位置,向右,向左移动两位就是乘以4第六行return(int)(t1+t2);这行没什么好说的,就是返回一个int类型的bin_num
