PHP のメモリ管理は 2 つの部分に分かれています。最初の部分は、参照カウント、コピーオンライト、その他のアプリケーション指向の管理です。紹介したいのは、使用可能なメモリの管理方法、メモリの割り当て方法など、zend_alloc で記述されている PHP 自体のメモリ管理です
なお、戦略以前に PHP のメモリ管理に関する情報が存在しないため、なぜこれを書く必要があるのかについて説明します。通常、拡張機能を開発したり PHP のバグを修正したりする場合、PHP 開発チームの多くの友人はこの部分についてよく理解していません。なので、具体的に書く必要があると思います
基本的な概念については、コードを見ればわかりやすいので、詳しくは説明しません。 ここでは、簡単ではない点を中心に紹介します。コードを見れば理解できるのに、なぜそんなことを言うのですか?笑、記事を書く前に、作業の重複を避けるために既存の情報を検索したところ、TIPI プロジェクトのこの部分の説明がたくさんあることがわかりました。なので、この部分はコードを見ても分かりにくいと思います
現在、英語版の紹介文も作成中です: Zend MM
Zend Memory Manager、以下Zend Memory Managerと呼びます。 Zend MM は、PHP におけるメモリ管理のロジックです。データ構造: zend_mm_heap:
Zend MM はメモリを小さなブロック メモリと大きなブロック メモリの 2 種類に分け、小さなメモリ ブロックではこの部分が最もよく使用されるため、大きなメモリ ブロックでは高いパフォーマンスが追求されます。
そのため、小さなメモリ ブロックに対して、PHP ではキャッシュ メカニズムも導入しています:
Zend MM はキャッシュを通じて可能な限りそれを行うことを望んでおり、割り当ては 1 つの場所で見つけることができます。
理解するのが簡単ではない点の 1 つは、free_buckets:
Q: free_buckets 配列の長さが ZEND_MM_NUMBER_BUCKET なのはなぜですか?
A: これは、上の図の赤いボックスに示すように、PHP が固定長配列 ZEND_MM_NUMBER_BUCKET zend_mm_free_block を使用するトリックを使用しているためです。これは使用されないため、有用なデータ構造は next_free_block と prev_free_block だけです。したがって、メモリを節約するために、PHP は ZEND_MM_NUMBER_BUCKET * sizeof(zend_mm_free_block) サイズのメモリを割り当てず、ZEND_MM_NUMBER_BUCKET * (sizeof(*next_free_block) + sizeof のみを使用します)。 (*prev_free_block)) メモリのサイズ..
ZEND_MM_SMALL_FREE_BUCKET マクロの定義を見てみましょう:
<ol class="dp-xml"> <li class="alt"><span><span>#define ZEND_MM_SMALL_FREE_BUCKET(heap, index) </span></span></li> <li> <span> (zend_mm_free_block*) ((char*)&heap-</span><span class="tag">></span><span>free_buckets[index * 2] + </span> </li> <li class="alt"><span> sizeof(zend_mm_free_block*) * 2 - </span></li> <li><span> sizeof(zend_mm_small_free_block)) </span></li> </ol>
その後、Zend MM は prev と next ポインターのみが使用されることを保証するため、メモリ読み取りエラーは発生しません
。次に、理解しにくい 2 番目の点は、PHP によるlarge_free_buckets の管理です。まず割り当てを紹介します (この部分の TIPI プロジェクト チームの説明は少し曖昧で不明瞭です):
<ol class="dp-xml"><li class="alt"><span><span>static zend_mm_free_block *zend_mm_search_large_block(zend_mm_heap *heap, size_t true_size) </span></span></li></ol>
large_free_buckets は組み合わせであると言えます。ツリー構築と双方向リスト:
large_free_buckets は、メモリのインデックスを決定するマクロを使用します。特定のサイズが該当します:
<ol class="dp-c"><li class="alt"><span><span>#define ZEND_MM_LARGE_BUCKET_INDEX(S) zend_mm_high_bit(S) </span></span></li></ol>
zend_mm_high_bit は true_size の最上位ビット 1 のシリアル番号 (zend_mm_high_bit) を取得し、対応するアセンブリ命令は bsr です (ここで、TIPI プロジェクト エラーの説明は次のとおりです)。サイズの桁数を計算し、戻り値はサイズ - 1'') のバイナリ コード内の 1 の数です。
つまり、large_free_buckets の各要素は、メモリへのポインターのサイズへのポインターを維持します。インデックス 1 のサイズに対応するブロック。 ああ、これは少し複雑です。例:
たとえば、large_free_buckets[2] の場合、サイズ 0b1000 から 0b1111 のメモリのみが保存されます。別の例:large_free_buckets [6]。 、0b10000000 から 0b11111111 までのサイズのメモリへのポインタを保存します。
このようにして、メモリを再割り当てするときに、Zend MM は検索に最適な領域を素早く見つけることができます。
そして、各要素も 2 です。 -way リストは同じサイズのメモリ ブロックを維持し、左右の子 (child[0] と child[1]) はそれぞれキー値 0 と 1 を表します。このキー値は何を参照しますか?
例として、true_size が 0b11010 のメモリを PHP に適用しました。いくつかの手順を実行した後、PHP は、適切なメモリを見つけるために zend_mm_search_large_block を実行しました。 , true_sizeに対応するインデックスを計算します。計算方法は前に説明したとおりです。 ZEND_MM_LARGE_BUCKET_INDEX
2. 然后在一个位图结构中, 判断是否存在一个大于true_size的可用内存已经存在于large_free_buckets, 如果不存在就返回:
<ol class="dp-c"> <li class="alt"><span><span>size_t bitmap = heap->large_free_bitmap >> index; </span></span></li> <li> <span class="keyword">if</span><span> (bitmap == 0) { </span> </li> <li class="alt"> <span> </span><span class="keyword">return</span><span> NULL; </span> </li> <li><span>} </span></li> </ol>
3. 判断, free_buckets[index]是否存在可用的内存:
<ol class="dp-c"><li class="alt"><span><span class="keyword">if</span><span> (UNEXPECTED((bitmap & 1) != 0)) </span></span></li></ol>
4. 如果存在, 则从free_buckets[index]开始, 寻找最合适的内存, 步骤如下:
4.1. 从free_buckets[index]开始, 如果free_buckets[index]当前的内存大小和true_size相等, 则寻找结束, 成功返回.
4.2. 查看true_size对应index后(true_size << (ZEND_MM_NUM_BUCKETS - index))的当前最高位, 如果为1. 则在free_buckets[index]->child[1]下面继续寻找, 如果free_buckets[index]->child[1]不存在, 则跳出. 如果true_size的当前最高位为0, 则在free_buckets[index]->child[0]下面继续寻找, 如果free_buckets[index]->child[0]不存在, 则在free_buckets[index]->child[1]下面寻找最小内存(因为此时可以保证, 在free_buckets[index]->child[1]下面的内存都是大于true_size的)
4.3. 出发点变更为2中所述的child, 左移一位ture_size.
5. 如果上述逻辑并没有找到合适的内存, 则寻找最小的”大块内存”:
<ol class="dp-c"> <li class="alt"><span><span class="comment">/* Search for smallest "large" free block */</span><span> </span></span></li> <li><span> best_fit = p = heap->large_free_buckets[index + zend_mm_low_bit(bitmap)]; </span></li> <li class="alt"> <span> </span><span class="keyword">while</span><span> ((p = p->child[p->child[0] != NULL])) { </span> </li> <li> <span> </span><span class="keyword">if</span><span> (ZEND_MM_FREE_BLOCK_SIZE(p) < ZEND_MM_FREE_BLOCK_SIZE(best_fit)) { </span></li><li class="alt"><span> best_fit = p; </span></li><li><span> } </span></li><li class="alt"><span> } </span></li></ol>
注意上面的逻辑, (p = p->child[p->child[0] != NULL]), PHP在尽量寻找最小的内存.
为什么说, large_free_buckets是个键树呢, 从上面的逻辑我们可以看出, PHP把一个size, 按照二进制的0,1做键, 把内存大小信息反应到了键树上, 方便了快速查找.
另外, 还有一个rest_buckets, 这个结构是个双向列表, 用来保存一些PHP分配后剩下的内存, 避免无意义的把剩余内存插入free_buckets带来的性能问题(此处, TIPI项目错误的描述为: “这是一个只有两个元素的数组。 而我们常用的插入和查找操作是针对第一个元素,即heap->rest_buckets[0]“).
作者: Laruence( ) 原文地址: