Upgrade to Pro — share decks privately, control downloads, hide ads and more …

入門ZendMemoryManager

chobie
March 15, 2014
3.4k

 入門ZendMemoryManager

入門しきれなかったZendMemoryManager

Sample:
https://gist.github.com/chobie/9563266

chobie

March 15, 2014
Tweet

Transcript

  1. Zend  MMを調べるかに⾄至るまで •  Reactを始めとしたライブラリの台頭 •  PECL  event等のevent系拡張の出現 •  daemonや⼤大規模なバッチ処理理の実⾏行行 PHPを使ってシングルスレッドで⼤大量量にコネクションを処理理

    したり、⼤大量量のデータを扱う事がより⼀一般化してきた。しか しそれと同時に⾊色々課題も⾒見見つかってきた ・GCの範囲はどこまでか正確に知る必要が出てきた ・PHPの変数がどこまで⽣生きていけるか知る必要が出てきた なぜ私が
  2. おさらい:  SAPI •  いろんなサーバーやアプリケーションに 組み込みやすいようにServer  APIという のがある •  cli,  apache,

     fpm等全て別のSAPIとして 実装 •  モジュール初期化、リクエスト初期化、 などのライフサイクルの概念念がある 詳しくはdo_akiさんの相模原PHP  vol1での発表を参照ください
 h.p://d.hatena.ne.jp/do_aki/20140124/1390520996  
  3. おさらい:  変数 •  $a  =  1;などの変数はPHPの内部でzval型 で表現される typedef  union  {

             long  lval;          dobule  dval;          struct  {                  char  *val;                  int  len;          }  str;          HashTable  *ht;          zend_̲object_̲value  *obj;          zend_̲ast  *ast;    }  zvalue_̲value; typedef  _̲zval_̲struct  {    zvalue_̲value  value;    zend_̲uint  refcount_̲_̲gc;    zend_̲uchar  type;    zend_̲uchar  is_̲ref_̲_̲gc; }  zval;
  4. おさらい:  変数 (zval)  $a  =  {    value  =  {

           lval  =  1        dval  =  4.9406564584124654E-‐‑‒324        str  =  (val  =  0x0000000000000001,  len  =  1)        ht  =  0x0000000000000001        obj  =  {            handle  =  1            handlers  =  0x0000000000000001        }        ast  =  0x0000000000000001    }    refcount_̲_̲gc  =  1    type  =  '\x01'    is_̲ref_̲_̲gc  =  '\0'
  5. おさらい:  変数 (zval)  $a  =  {    value  =  {

           lval  =  1        dval  =  4.9406564584124654E-‐‑‒324        str  =  (val  =  0x0000000000000001,  len  =  1)        ht  =  0x0000000000000001        obj  =  {            handle  =  1            handlers  =  0x0000000000000001        }        ast  =  0x0000000000000001    }    refcount_̲_̲gc  =  1    type  =  '\x01'    is_̲ref_̲_̲gc  =  '\0' lval  =  1が実際の値 refcount__gc  =  1がReference  Count   type  =  \x01がLongであることを意味する  
  6. おさらい:  Garbage  Collection •  Reference  Count⽅方式(従来) – 内部的に参照数を保持し0になったらfree – 循環参照に対応出来ない •  Concurrent

     Cycle  Collection  in   Reference  Counted  System(>=5.3) – 内部バッファにルートを貯めてMarkSweap – 循環参照に対応 – 詳しくはBacon01Concurrentでググろう PHPのGCはこの2種類の組み合わせ
  7. PHPでよく⾒見見かけるリーク •  PHP5.2以下を運⽤用する場合よくメモリ リークしてた – PHP内部,  extensionのメモリ割り当て/開 放の不不備 – ユーザーコード実装者の経験不不⾜足 •  バッチ処理理でselect

     *  from〜~とかで死亡 •  循環参照しまくりでメモリ利利⽤用量量がもりもり •  そもそも不不要なデータを開放する概念念がない⼈人
  8. メモリリーク事例例: •  PHPでHTTP  Serverを書いた –  コネクションハンドリング周りで永遠にクライア ント関連の不不要なデータを参照し続けリクエスト が増えるたびにメモリ利利⽤用量量がもりもり増えて最 終的に殺される – 

    Callback地獄でどれを開放していいか設計者もわ からない増体で書いてしまい、メモリ利利⽤用量量が (ry •  PHPでバッチ処理理を書いた –  MySQL等から全データ取得するAPIをコールして データ取り過ぎで殺される –  複雑すぎるデータ構造で開放出来なかった
  9. 簡単なまとめ:  GC •  最近のPHPでリーク(してるように⾒見見える)の は⼤大抵ユーザーのコード、設計が悪い –  昔はinternal側の実装で発⽣生したリークは確かに多 かった •  Full

     GCではなくConcurrent  GCなのでふんわり 性能が落落ちる。zend.enable_̲gcで無効にでき る。当然循環参照系の解放ができなくなるので unset地獄となる。使わないなら使わないでき ちんと設計考えて書こう。 PHPを使う理理由の⼤大半が細かいことを気にせずに楽したい、という のだと思うので迂回⽅方法があるならそっち使うのがオススメ
  10. 多分眠くなったと思うので •  デモ –  libuv  extensionを使ったHTTP  Serverのベンチ マーク –  libuv

     extensionとwebsocket  frame  extension を使ったWebsocket実装の確認 https://github.com/chobie/php-‐‑‒uv https://github.com/chobie/php-‐‑‒ websocketframe ※こういう実際のアプリケーションではないベンチマークとかあまり役に ⽴立立たないので気にしないように
  11. Zend  MMの⽬目的 •  メモリ割り当ての効率率率化 – より速いメモリ管理理 •  mallocのコール回数の抑制 –  コンテキストスイッチの削減 – 

    フラグメンテーションの抑制 –  CPU利利⽤用率率率の削減 – より少ないメモリ割り当て •  フラグメンテーションが少なくなるから結果的に少なくなる •  cacheによるメモリの再利利⽤用 – 安全なメモリ管理理 ZendMMを使うことでPHP実⾏行行中のメモリ利利⽤用量量の詳細が簡単に把握できる。ま た管理理を⾃自前で⾏行行うことにより割り当てはより⾼高速に⾏行行うことができる
  12. Zend  MMのレイヤ Zend  Memory  Manager Libc  malloc   mmap custom

    kernel Physical  Memory Zend  MMはメモリ管理理の抽象レイヤある為、⾃自分の使いたい メモリアロケーターも組み込むことができる (jpauliの図を参考)
  13. 例例えば ex.php <?php ini_̲set(“memory_̲limit”,  -‐‑‒1); $result  =  array(); for  ($i

     =  0;  $i  <  1024;  $i++)  {    for  ($j  =  0;  $j  <  1024;  $j++)  {      $result[]  =  sqrt($i*$i  +  $j+$j)  ;    } } echo  memory_̲get_̲usage(true); 愚直に考えると少なくとも1,048,576+α回以上のメモリの割り当てが 必要になるように見える
  14. 例例えば macbook%  time  USE_̲ZEND_̲ALLOC=0  /usr/bin/php   -‐‑‒dmemory_̲limit=-‐‑‒1  ex.php 0.63s  user

     0.06s  system  99%  cpu  0.690  total macbook%  time  USE_̲ZEND_̲ALLOC=1  /usr/bin/php   -‐‑‒dmemory_̲limit=-‐‑‒1  ex.php 0.43s  user  0.07s  system  99%  cpu  0.499  total USE_̲ZEND_̲ALLOC環境変数でZendMMの有効、無効が切切り替えられ る。Zend  MMがあるおかげで⼤大抵の場合メモリ管理理の効率率率化により⾼高 速化が期待できることがわかる。 (プロダクション環境の場合zend_̲mm_̲heap  corruptedが出るからZendMM無効にし よう、と安直に考えずきちんとパフォーマンス取ってから考えた⽅方がいい。迂回⽅方法は いくらでもある)
  15. ZendMMに関連する構造体 zend_mm_heap    int            

                         use_zend_alloc;    void                              *(*_malloc)(size_t);    void                                (*_free)(void*);    void                              *(*_realloc)(void*,  size_t);    size_t                            free_bitmap;    size_t                            large_free_bitmap;    size_t                            block_size;    size_t                            compact_size;    zend_mm_segment        *segments_list;    zend_mm_storage        *storage;    size_t                            real_size;    size_t                            real_peak;    size_t                            limit;    size_t                            size;    size_t                            peak;    size_t                            reserve_size;    void                              *reserve;    int                                  overflow;    int                                  internal;    unsigned  int                cached;    zend_mm_free_block  *cache[ZEND_MM_NUM_BUCKETS];    zend_mm_free_block  *free_buckets[ZEND_MM_NUM_BUCKETS*2];    zend_mm_free_block  *large_free_buckets[ZEND_MM_NUM_BUCKETS];    zend_mm_free_block  *rest_buckets[2];    int                                  rest_count;    struct  {      int  count;      int  max_count;      int  hit;      int  miss;    }  cache_stat[ZEND_MM_NUM_BUCKETS+1];   zend_mm_free_block    zend_mm_block_info  info;    unsigned  int  magic;    THREAD_T  thread_id;    struct  _zend_mm_free_block  *prev_free_block;    struct  _zend_mm_free_block  *next_free_block;      struct  _zend_mm_free_block  **parent;    struct  _zend_mm_free_block  *child[2];   zend_mm_block_info    size_t  _cookie;    size_t  _size;    size_t  _prev;   zend_mm_small_free_block    zend_mm_block_info  info;    unsigned  int  magic;    THREAD_T  thread_id;    struct  _zend_mm_free_block  *prev_free_block;    struct  _zend_mm_free_block  *next_free_block;  
  16. ZendMM関連の定数(OSXの場合) ZEND_MM_ALIGNMENT=8   ZEND_MM_ALIGNMENT_LOG2=3   ZEND_MM_MIN_SIZE=4   ZEND_MM_MAX_SMALL_SIZE=608   ZEND_MM_ALIGNED_HEADER_SIZE=88

      ZEND_MM_ALIGNED_FREE_HEADER_SIZE=56   ZEND_MM_MIN_ALLOC_BLOCK_SIZE=96   ZEND_MM_ALIGNED_MIN_HEADER_SIZE=96   ZEND_MM_ALIGNED_SEGMENT_SIZE=16   これらの定数は環境により変わったりする。上記はDEBUG版のため多め。 zend_mm_startup_exのifdef  blockにデバッグ表示が書いてあるので有効にすれば
 一覧で出せる
  17. ZendMMに関連するC-‐‑‒API •  emalloc •  safe_̲emalloc  (ざっくりいうとoverflowチェック版) •  efree •  ecalloc

    •  erealloc •  erealloc_̲recoverable •  estrdup •  estrndup •  zend_̲mem_̲block_̲size emalloc等のAPIでZend  MM経由でメモリ管理を行う   persistent系のpemalloc等はzend  MM経由ではなく直接malloc を扱う(メモリ管理の対象ではない)
  18. ZendMMに関連するC-‐‑‒API •  zend_̲mm_̲startup •  zend_̲mm_̲shutdown •  _̲zend_̲mm_̲alloc •  _̲zend_̲mm_̲free • 

    _̲zend_̲mm_̲realloc zend_mm_mem_handlers    const  char  *name;    zend_mm_storage*  (*init)(void  *params);    void  (*dtor)(zend_mm_storage  *storage);    void  (*compact)(zend_mm_storage  *storage);    zend_mm_segment*  (*_alloc)(zend_mm_storage  *storage,  size_t  size);    zend_mm_segment*  (*_realloc)(zend_mm_storage  *storage,                                                                                                                                                                zend_mm_segment  *ptr,  size_t  size);    void  (*_free)(zend_mm_storage  *storage,  zend_mm_segment  *ptr);   これらのAPIは直接扱うことは殆どなく、ZendEngineから利用さ れる。zend_mm_mem_handlers型はzend_alloc.cに定義がある mem_handlers[]に直接メモリハンドラを追加することで独自のメ モリハンドラが利用できるようになる
 (例えば、jemallocやtcmallocを組み込んだりできる。こういった
 取り組みはkrakjoeやjpauliが試している)
  19. ZendMMに関連するPHP  API •  memory_̲get_̲usage([real]) •  memory_̲get_̲peak_̲usage([real]) •  memory_̲limit ZendMM経由でメモリ管理理を⾏行行うことでセグメントの利利⽤用量量 やmemory_̲limitなどの制限が⾏行行える

    (Fatal  Error:  Out  of  memoryはZend  MMが⾏行行っている機能) ZendMM経由なのでextensionが独⾃自に⾏行行なったmallocや3rd ライブラリが利利⽤用しているメモリ利利⽤用量量までは把握出来ない (実際のメモリ利利⽤用量量を計測したいならgetrusage等の別の⽅方法を使う必要がある)
  20. メモリが割り当てられるまで •  渡されたサイズのpadding  sizeを計算する –  ZEND_MM_MIN_SIZE未満ならZEND_MM_ALIGNED_MIN_HEADER_SIZE、 それ以上なら (($size  +  $ZEND_MM_ALIGNED_HEADER_SIZE)

     +   $ZEND_MM_ALIGNMENT  -­‐  1)  &  $ZEND_MM_ALIGNMENT_MASK;   •  要するに大体size+header+8bytes刻みがpadding  sizeとなる •  padding  sizeがZEND_MM_ALIGNED_MIN_HEADER_SIZEならcache から割り当てる。cacheが使えない場合はfreebucketsから最小サイ ズのメモリブロックを探して割り当てる。
 padding  sizeがZEND_MM_ALIGNED_MIN_HEADER_SIZE以上の場 合はlarge_free_bucketsから割り当て可能な最小サイズのメモリブ ロックを探し割り当てる。なければrest_blockから、それでも見つか らなければ新規にメモリを確保してrest_blockに割り当てそこから 返す ざっくり 少し複雑な仕組みなので興味がある人はDebug版でビルドして
 _zend_mm_alloc_intあたりをbreak  pointにして読み進めるのが良い