>  기사  >  백엔드 개발  >  PHP의 작은 메모리 사양 계산(코드 예)

PHP의 작은 메모리 사양 계산(코드 예)

不言
不言앞으로
2019-02-25 10:01:162154검색

이 기사는 PHP의 작은 메모리 사양 계산에 관한 것입니다(코드 예제). 필요한 친구들이 참고할 수 있기를 바랍니다.

소형 메모리 할당 계산 bin_num

PHP 소스 코드에는 소규모 메모리 사양 계산이 있는데, 특히 Zend/zend_alloc.c의 zend_mm_small_size_to_bin 함수에 그 목적이 있습니다. 크기를 전달하고 해당 사양을 계산합니다. 코드 보기:

if (size <= 64) {
    /* we need to support 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, 크기는 다음보다 작습니다. 또는 64와 같음

  • 2. 크기가 64보다 큰 상황;

아래에서는 이 두 가지 상황을 자세히 분석해 보겠습니다.

64보다 작거나 같은 크기의 경우

  • ZEND_MM_BINS_INFO를 참조하세요. 이 매크로는 크기가 64보다 작거나 64와 같음 8씩 증가하는 산술 시퀀스이므로 크기를 8로 나눈 값을 사용합니다(소스 코드에서 오른쪽으로 3자리 이동) size >> 3<p>#🎜 🎜#<code>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;
    return (int)(t1 + t2);

    初始懵逼

    • 初看这个代码,容易一脸懵逼,这些t1 t2 都是啥啊

    • 不过不用怕,我们一点点来分析

    步骤分析

    /* num, size, count, pages */
    #define ZEND_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; 这个是边界情况,跟前面一样,后面出现的size暂且都认为已近减一了

    • 假设不看这个源码,我们要实现在ZEND_MM_BINS_INFO中找到对应的bin_num

    • ZEND_MM_BINS_INFO得知后续的增加4个为一组,分别为

    2^4, 2^5, 2^6...
    • 有了这个分组信息的话,我们要找siez对应的bin_num

      • 找到这个size属于哪一组

      • 并且size在组内的偏移是多少

      • 计算组的起始位置

    • 那现在问题转换成了上面3个小问题,我们一个一个来解决

    找到size属于哪一组
    • 最简单的办法就是比大小是吧,可以使用if...else 来一个一个比,但是显然php源码不是这样干的,那我们还有什么其它的办法呢?

    • 我们看十进制看不出来什么名堂,就把这些值转成二进制看看吧

    64  | 100 0000
    80  | 101 0000
    96  | 110 0000
    112 | 111 0000
    
    128 | 1000 0000
    160 | 1010 0000
    192 | 1100 0000
    224 | 1110 0000
    
    256 | 1 0000 0000
    320 | 1 0100 0000
    384 | 1 1000 0000
    448 | 1 1100 0000
    
    .....
    • 我们看下上面的二进制,会发现每组的内的二进制长度相等,并且后面每个都比前面多一位

    • 那就是说我们可以计算二进制的长度来决定它的分组,那么二进制的长度又是啥呢,其实就是当前二进制的最高位为1的位数

    • 那么问题又转换成了求二进制中最高位的1的位数

    • 下面给出php源码的解法,这里暂时不对其解析,只要知道它返回的是二进制中最高位的1的位数

    int n = 16;
    if (size <= 0x00ff) {n -= 8; size = size << 8;}
    if (size <= 0x0fff) {n -= 4; size = size << 4;}
    if (size <= 0x3fff) {n -= 2; size = size << 2;}
    if (size <= 0x7fff) {n -= 1;}
    return n;
    • 假设我们申请的size为65,那么这里的n返回7

    计算size在组内的偏移量
    • 这个简单,直接用size减去每组的起始siez大小然后除以当前组内的差值(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 - 4
    • 那是不是可以用7、8、9减去3得到4、5、6,这样我们就可以根据它在哪一组的信息得到当前组的差值(16、32、64...)

    • 当size为65时,偏移量是不是就是

    (64-64) / 2^4 = 0
    计算组的起始位置
    • 现在我们有了偏移量的信息,假定我们分组是1、2、3

    • 那是不是就是用最高位的1的位数减去6就可以得到分组信息了

    • 得到分组信息之后,怎么知道每组的起始位置呢

    • 我们知道起始位置分别是8、12、16...它也是一个等差数列,就是4n+4

      하지만 크기가 8, 16 등과 같은 상황을 고려해야 하므로 (size - 1) >>3#🎜 🎜#</pre> <li>#🎜 🎜#그러면 0의 경우를 고려해야 하므로 소스코드에서 <code>-1 처리는 !!size code>이고 크기가 0이면 <code>!!0 = 0입니다. 그래서 size가 0일 때 -1-0으로 변환되고, 마지막으로 소스 코드의 표현은 (size - !!size) &gt ;&gt입니다. ; 3

      #🎜🎜##🎜🎜#크기가 64보다 큰 경우#🎜🎜#
      1 t1 = size - 1;
      2 t2 = zend_mm_small_size_to_bit(t1) - 3;
      3 t1 = t1 >> t2;
      4 t2 = t2 - 3;
      5 t2 = t2 << 2;
      6 return (int)(t1 + t2);

      초기 혼란

      #🎜🎜## 🎜🎜##🎜🎜#이 코드를 처음 보면 헷갈리기 쉽습니다. #🎜🎜##🎜🎜##🎜🎜#

      단계 분석

      /* higher set bit number (0->N/A, 1->1, 2->2, 4->3, 8->4, 127->7, 128->8 etc) */
      
      int n = 16;
      if (size <= 0x00ff) {n -= 8; size = size << 8;}
      if (size <= 0x0fff) {n -= 4; size = size << 4;}
      if (size <= 0x3fff) {n -= 2; size = size << 2;}
      if (size <= 0x7fff) {n -= 1;}
      return n;
      #🎜🎜##🎜🎜##🎜🎜#size = size - 1; 이것이 경계입니다. 상황은 이전과 동일하므로, 나중에 나타나는 크기는 고려됩니다. 해당 bin_num#🎜🎜##🎜🎜##🎜🎜##🎜🎜#은 ZEND_MM_BINS_INFO에서 발견되며 이후 4가 추가됩니다. #🎜🎜## 🎜🎜##🎜🎜#
      (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
      #🎜🎜##🎜🎜##🎜🎜#이 그룹화 정보를 사용하여 siez#🎜🎜##🎜🎜에 해당하는 bin_num을 찾아야 합니다. #
        #🎜🎜##🎜🎜#이 크기가 속한 그룹 찾기 #🎜🎜##🎜🎜##🎜 🎜##🎜🎜# 및 크기는 무엇입니까? 이제 문제는 위의 3가지 작은 문제로 변환됩니다. 하나씩 해결해 보겠습니다 #🎜🎜##🎜🎜##🎜🎜#< h5>사이즈가 속한 그룹 찾기#🎜🎜##🎜🎜## 🎜🎜#가장 간단한 방법은 사이즈를 비교하는 것이겠죠. if...else를 사용하여 하나씩 비교할 수 있겠죠? 하지만 분명히 PHP 소스 코드는 이와 같이 작동하지 않습니다. 그렇다면 다른 방법이 있습니까? #🎜🎜##🎜🎜##🎜🎜##🎜🎜#십진수에는 흥미로운 내용이 없으므로 이 값을 이진수로 변환하여 살펴보겠습니다#🎜🎜##🎜🎜##🎜🎜 #
        binnum = (4n + 4) + (size / 2^n - 4)
        
        binnum = 4n + size / 2^n
        #🎜 🎜##🎜🎜##🎜🎜#위의 바이너리 코드를 보면, 각 그룹의 바이너리 코드의 길이는 동일하고, 다음의 각 그룹은 그보다 한 자리 더 많은 것을 알 수 있습니다. 이전 것#🎜🎜##🎜🎜##🎜🎜# #🎜🎜#즉, 이진수의 길이를 계산하여 그룹화를 결정할 수 있다는 뜻입니다. 그렇다면 이진수의 길이는 실제로 자릿수입니까? 여기서 현재 바이너리의 최상위 비트는 1#🎜🎜 ##🎜🎜##🎜🎜##🎜🎜# 그러면 문제는 가장 높은 의 자릿수를 찾는 것으로 변환됩니다. 1 in 바이너리 #🎜🎜##🎜🎜##🎜🎜##🎜 🎜#PHP 소스 코드에 대한 솔루션은 아래에 나와 있습니다. 여기서는 당분간 분석하지 않겠습니다. 이진수#🎜🎜##🎜🎜##🎜🎜#
        binnum = (4n + 4) + (size / 2^n - 4)
        
        binnum = 4n + size / 2^n
        #🎜🎜##🎜🎜##🎜🎜#에서 가장 높은 1의 자릿수를 반환한다고 가정합니다. 우리가 적용하는 크기는 65이고 여기서 n은 7을 반환합니다.#🎜🎜##🎜🎜##🎜🎜#
        그룹 내 크기 오프셋 계산 금액
        #🎜🎜##🎜🎜##🎜 🎜#이것은 간단합니다. size를 사용하여 각 그룹의 시작 시에즈 크기를 뺀 다음 이를 현재 그룹의 차이(16, 32, 64...), 즉 (size-64)로 나누면 됩니다. )/16(크기-128)/32(크기-256)/64#🎜🎜##🎜🎜##🎜🎜##🎜🎜# 이제 이전 단계에서 각 그룹은 7, 8, 9...입니다. 이제 이러한 데이터를 사용하여 그룹 내 오프셋을 계산하는 방법을 살펴보겠습니다. 🎜🎜#rrreee#🎜🎜##🎜🎜##🎜🎜#7, 8, 9 코드에서 3</을 빼면 <code>4, 5, 6<을 얻을 수 있나요? /code>를 사용하여 현재 그룹(16, 32, 64...)이 어느 그룹에 속해 있는지에 대한 정보를 바탕으로 차이점을 알 수 있습니다. #🎜🎜##🎜 🎜##🎜🎜##🎜🎜 #크기가 65일 때 오프셋은 #🎜🎜##🎜🎜##🎜🎜#rrreee<h5>그룹의 시작 위치 계산</h5>#🎜 🎜##🎜🎜##🎜🎜# 이제 그룹화가 1, 2, 3이라고 가정하고 오프셋 정보를 얻었습니다.#🎜🎜##🎜🎜##🎜🎜##🎜🎜#이것이 우리에게 필요한 것입니까? 그룹화 정보를 얻으려면 #🎜🎜##🎜🎜##🎜🎜##🎜🎜#그룹화 정보를 얻은 후 각 그룹의 시작 위치를 어떻게 알 수 있나요? #🎜🎜 ##🎜🎜##🎜🎜##🎜🎜# 우리는 시작 위치가 <code>8, 12, 16...이라는 것을 알고 있습니다. 또한 산술 수열은 4n+4입니다. code>#🎜🎜##🎜🎜##🎜🎜##🎜🎜#size=65#🎜🎜#의 예를 살펴보겠습니다.
        • 计算的偏移量是0

        • 计算的起始位置是4*1 + 4 = 8

        • 所以当size=65的bin_num就是起始位置加上偏移量 8 + 0 = 8

      • 我们再看一个size=129的例子

        • 二进制中最高位的1的位数为8

        • 然后用8减去3得到5

        • (129 - 1 - 32 * 4) / 64 = 0

        • 偏移量是

        • 计算起始位置是 4 * 2 + 4 = 12

        • 两者相加就是 12 + 0 = 0

      • size=193

        • 二进制中最高位的1的位数为8

        • (193 - 1 - 32 * 4) / 64 = 2

        • 偏移量是

        • 计算起始位置是 4 * 2 + 4 = 12

        • 两者相加就是 12 + 2 = 14

      • size=1793

        • 二进制中最高位的1的位数为11

        • (1793 - 1 - 256 * 4) / 256 = 3

        • 偏移量是

        • 计算起始位置是 4 * 5 + 4 = 24

        • 两者相加就是 24 + 3 = 27

      代码分析

      php实现代码

      1 t1 = size - 1;
      2 t2 = zend_mm_small_size_to_bit(t1) - 3;
      3 t1 = t1 >> t2;
      4 t2 = t2 - 3;
      5 t2 = t2 << 2;
      6 return (int)(t1 + t2);

      第一行

      • t1 = size - 1;

      • 是为了考虑size为64、128...这些边界情况

      第二行

      • t2 = zend_mm_small_size_to_bit(t1) - 3;

      • 这里调用了zend_mm_small_size_to_bit这个函数,我们看看这个函数

      /* higher set bit number (0->N/A, 1->1, 2->2, 4->3, 8->4, 127->7, 128->8 etc) */
      
      int n = 16;
      if (size <= 0x00ff) {n -= 8; size = size << 8;}
      if (size <= 0x0fff) {n -= 4; size = size << 4;}
      if (size <= 0x3fff) {n -= 2; size = size << 2;}
      if (size <= 0x7fff) {n -= 1;}
      return n;
      • 看注释我们就知道这个函数是用来返回当前size二进制中最高位1的位数,具体的做法呢其实就是二分法

      • 我们通过zend_mm_small_size_to_bit这个函数获取了size二进制中最高位1的位数,那么这个 -3 是什么神奇的操作呢

        (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...

        • 上问的分析中提到,我们计算size在组内的偏移量的公式

      第三行

      • t1 = t1 >> t2;</pre>

      • 把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

        위 내용은 PHP의 작은 메모리 사양 계산(코드 예)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제