【PHP源碼分析】small內存規格的計算

做者:李德php

small內存分配計算bin_num

在PHP源碼中,有一段對small內存規格的計算,具體在Zend/zend_alloc.c的zend_mm_small_size_to_bin函數中,其目的是傳入一個size,計算對應的規格。見代碼:函數

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);
}

能夠看出,這段代碼中分爲兩種狀況進行討論:code

  • 一、size小於等於64的狀況;
  • 二、size大於64的狀況;

下面咱們對這兩種狀況詳細分析下。內存

對於size小於等於64的狀況

  • ZEND_MM_BINS_INFO這個宏知道當size小於等於64的狀況是一個等差數列,遞增8,因此使用size除以8就行(源碼中是右移3位)size >> 3
  • 可是要考慮到size等於八、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大小而後除以當前組內的差值(1六、3二、64...)便可,也就是(size-64)/16 (size-128)/32 (size-256)/64
  • 如今來看看上一步中的返回的值,每一個組分別是七、八、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
  • 那是否是能夠用七、八、9減去3獲得四、五、6,這樣咱們就能夠根據它在哪一組的信息獲得當前組的差值(1六、3二、64...)
  • 當size爲65時,偏移量是否是就是
(65-64) / 2^4 = 0
計算組的起始位置
  • 如今咱們有了偏移量的信息,假定咱們分組是一、二、3
  • 那是否是就是用最高位的1的位數減去6就能夠獲得分組信息了
  • 獲得分組信息以後,怎麼知道每組的起始位置呢
  • 咱們知道起始位置分別是八、十二、16...它也是一個等差數列,就是4n+4
  • 咱們在看看size=65的那個例子數學

    • 計算的偏移量是0
    • 計算的起始位置是4*1 + 4 = 8
    • 因此當size=65的bin_num就是起始位置加上偏移量 8 + 0 = 8
  • 咱們再看一個size=129的例子it

    • 偏移量是class

      • 二進制中最高位的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爲6四、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在組內的偏移量的公式
    (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
    • 這裏獲取二進制的位數是七、八、9...經過 -3 的操做來獲取相應的 四、五、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
相關文章
相關標籤/搜索