【PHPソース分析】smallメモリ仕様の計算

7812 ワード

作者:李徳
smallメモリ割当て計算bin_num
PHPソースには、Zend/zend_でsmallメモリ仕様の計算があります.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);
}

このコードでは、次の2つのケースに分けて説明されています.
  • 1、sizeが64以下の場合.
  • 2、sizeが64より大きい場合.

  • 以下、この2つの状況を詳しく分析します.
    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;
    return (int)(t1 + t2);

    最初は愚かだった
  • 初めてこのコードを見て、顔がぼんやりしていて、これらのt 1 t 2はすべて何です
  • でも恐れる必要はありません.私たちは少しずつ
  • を分析します.
    ステップ分析
    /* 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つを1組とし,それぞれ
  • とした.
    2^4, 2^5, 2^6... 
  • このパケット情報があればsiez対応bin_を探しますnum
  • このsizeがどのグループに属しているかを見つけます
  • そしてsizeのグループ内のオフセットはどのくらい
  • ですか.
  • 計算グループの開始位置
  • それは今問題が上の3つの小さな問題に変換されて、私たちは1つずつ
  • を解決します.
    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の場合、オフセット量は
  • であるか否かである.
    (65-64) / 2^4 = 0

    計算グループの開始位置
  • オフセット量の情報が得られ、パケットが1、2、3
  • であると仮定する
  • それは最上位の1の桁数から6を減算パケット情報が得られる
  • ではないか.
  • パケット情報を取得した後、どのようにして各グループの開始位置を知っていますか
  • 開始位置はそれぞれ8、12、16...であり、等差数列でもあることを知っています.4n+4
  • です.
  • size=65の例を見てみましょう
  • で算出するオフセット量は0
  • である.
  • 計算の開始位置は4*1 + 4 = 8
  • である.
  • だからsize=65のbin_numは、開始位置にオフセット量8 + 0 = 8を加える
  • である.
  • size=129の例を見てみましょう
  • オフセット量Yes
  • バイナリにおける最上位の1のビット数は8
  • である.
  • そして8から3を引く5
  • を得る.
  • (129 - 1 - 32 * 4) / 64 = 0

  • 計算開始位置は4 * 2 + 4 = 12
  • である.
  • 両者を加算と12 + 0 = 0
  • となる.
  • size=193
  • オフセット量Yes
  • バイナリにおける最上位の1のビット数は8
  • である.
  • (193 - 1 - 32 * 4) / 64 = 2

  • 計算開始位置は4 * 2 + 4 = 12
  • である.
  • 両者を加算と12 + 2 = 14
  • となる.
  • size=1793
  • オフセット量Yes
  • バイナリにおける最上位の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であることを考慮するため...これらの境界状況
  • 2行目
  • 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
  • ここで取得したバイナリのビット数は7、8、9です.-3の操作により、対応する4、5、6を取得する.


  • 3行目
  • t1 = t1 >> t2;
  • t 1をt 2ビット右に移動すると、これは何の不思議な操作ですか?
  • 最後にbinを計算しますnumの数学式は、各グループの開始位置にグループ内のオフセット量
  • を加えたものに等しい.
    binnum = (4n + 4) + (size / 2^n - 4)
    
    binnum = 4n + size / 2^n
  • ですので3行目の意味は分かりますが、size右シフト2^n次は
  • です.
    4行目
  • t2 = t2 - 3;
  • この理解は、上記の各グループの開始位置を得る方法
  • を参照することができる.
    5行目
  • t2 = t2 << 2;
  • binを見てみましょうnumの計算式
  • binnum = (4n + 4) + (size / 2^n - 4)
    
    binnum = 4n + size / 2^n
  • それではこの行はよく理解して、各グループの開始位置4nを計算するのではないでしょうか.左に2桁移動するのは4
  • を乗じることです.
    6行目
  • return (int)(t1 + t2);
  • この行は何も言わないで、intタイプのbinを返しました.num