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

入門ZendMemoryManager

3169fb51728b8dc9d56675d3fb1a6de6?s=47 chobie
March 15, 2014
3.1k

 入門ZendMemoryManager

入門しきれなかったZendMemoryManager

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

3169fb51728b8dc9d56675d3fb1a6de6?s=128

chobie

March 15, 2014
Tweet

Transcript

  1. ⼊入⾨門Zend  Memory  Manager chobi_̲e ҋPHP #5

  2. ⾃自⼰己紹介 •  @chobi_̲e  (ちょびえ) •  職業:Web系でアプリケーション/インフ ラエンジニア •  専⾨門:PHP,  MySQLあたり広く浅く

    •  OSS:  PECL  ProtocolBuffers,  Sundown,   php-‐‑‒git2,  fluent-‐‑‒logger-‐‑‒php等
  3. 本⽇日のお題 ⼊入⾨門Zend  Memory  Manager 略略してZendMM 今⽇日はZend  Memory  Managerを読み始めるにあたって 必要な事柄をおさらいしつつ、Zend  MMのさわりのとこ

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

    したり、⼤大量量のデータを扱う事がより⼀一般化してきた。しか しそれと同時に⾊色々課題も⾒見見つかってきた ・GCの範囲はどこまでか正確に知る必要が出てきた ・PHPの変数がどこまで⽣生きていけるか知る必要が出てきた なぜ私が
  5. 新しい仕組みにより出てきた課題 •  PECL  ThreadによりPHPプロセス内で新 しくThreadを作成し、PHPVMが実⾏行行でき るようになった – マルチスレッド化の道筋が⾒見見えたが extension実装者側でPHPの変数がどのコン テキストまで⽣生きられるか知る必要が出てき た

    – というかマルチスレッドで実⾏行行するために zend_̲vmの知識識も必要になった
  6. 新しい仕組みにより出てきた課題 •  Reactなどの台頭によりPHPでWebServer を書くことも増えたが同時にメモリリー クに深刻に悩まされるようになった – 俺の実装が悪いのかお前の実装が悪いのかも はや判断がつかない •  PHPで書いたサーバーのどこがボトルネッ クか判断できる必要がでてきた

    – なんか遅いなー、きっとgcのせいじゃない? 知らないけどきっとそう。【本当に?】
  7. 本題に⼊入る前に いろいろなおさらい

  8. そもそもPHPとは •  Cで書かれたPHP⾔言語を動作させるプロセス –  Cは当然開発者がメモリ割り当てを管理理する必要 がある •  不不要なデータの開放に関してはGarbage  Collection側 で対応

    •  PHPの内部ではメモリ管理理(割当、開放)に関してZend   Memory  Managerが担当
  9. いろいろなおさらい SAPI

  10. おさらい:  SAPI •  いろんなサーバーやアプリケーションに 組み込みやすいようにServer  APIという のがある •  cli,  apache,

     fpm等全て別のSAPIとして 実装 •  モジュール初期化、リクエスト初期化、 などのライフサイクルの概念念がある 詳しくはdo_akiさんの相模原PHP  vol1での発表を参照ください
 h.p://d.hatena.ne.jp/do_aki/20140124/1390520996  
  11. おさらい:  Apache  SAPI •  所謂mod_̲php •  listen,  phpの初期化してfork – 基本MINITは親でしか呼ばれない – RINIT/RSHUTDOWNがリクエスト毎に⼦子プ

    ロセスで繰り返される(使いまわされる) – MaxRequestsPerChild超えるとkillして回収
  12. いろいろなおさらい PHPの変数について

  13. おさらい:  変数 •  $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;
  14. おさらい:  変数 (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'
  15. おさらい:  変数 (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であることを意味する  
  16. 簡単なまとめ:  変数 •  Cで書かれているので当然内部の型でPHP の変数等を表現している •  zval単体で表現できるのは数値と⽂文字列列 配列列やobjectの表現は別途HashTableや Objectが必要となる (普段はこの知識識必要ないけどThread使うときは必要)

    •  きっとhnwさんがzvalについて詳しくあとで解説してくれるはず
  17. いろいろなおさらい Garbage  Collection

  18. おさらい:  Garbage  Collection •  Reference  Count⽅方式(従来) – 内部的に参照数を保持し0になったらfree – 循環参照に対応出来ない •  Concurrent

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

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

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

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

     extensionとwebsocket  frame  extension を使ったWebsocket実装の確認 https://github.com/chobie/php-‐‑‒uv https://github.com/chobie/php-‐‑‒ websocketframe ※こういう実際のアプリケーションではないベンチマークとかあまり役に ⽴立立たないので気にしないように
  23. どうでしたか? PHPやればできるこ (てきとーにやるにはすげー楽)

  24. (ようやっと)本⽇日のお題 Zend  Memory  Manager

  25. Zend  MMの⽬目的 •  メモリ割り当ての効率率率化 – より速いメモリ管理理 •  mallocのコール回数の抑制 –  コンテキストスイッチの削減 – 

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

    kernel Physical  Memory Zend  MMはメモリ管理理の抽象レイヤある為、⾃自分の使いたい メモリアロケーターも組み込むことができる (jpauliの図を参考)
  27. 例例えば 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+α回以上のメモリの割り当てが 必要になるように見える
  28. 例例えば 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無効にし よう、と安直に考えずきちんとパフォーマンス取ってから考えた⽅方がいい。迂回⽅方法は いくらでもある)
  29. 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;  
  30. 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にデバッグ表示が書いてあるので有効にすれば
 一覧で出せる
  31. 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 を扱う(メモリ管理の対象ではない)
  32. 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が試している)
  33. 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等の別の⽅方法を使う必要がある)
  34. ZendMMに関連するPHP  API (jpauliのunderstanding  php  memoryより拝借) memory_get_usage(true  /  false)の違い

  35. ZendMMの構造 h.ps://wiki.php.net/internals/zend_mm    

  36. メモリが割り当てられるまで •  渡されたサイズの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にして読み進めるのが良い
  37. まとめ •  どの仕組みがどの領域なのか理解すること によって問題発生時の調査が早くできるよう になる •  正しい知識を得て自分たちのワークロードに あった解決方法を考えられるようになろう

  38. その他 ・[参考]jpauliによるPHPのメモリ管理の解説 h.p://www.slideshare.net/jpauli/understanding-­‐php-­‐memory 
 ・[参考]laurenceによるZend  MMの解説 h.ps://wiki.php.net/internals/zend_mm ・[参考]Dmitryによる新しいMemoryManagerの提案   h.ps://github.com/dstogov/php-­‐src/commits/xx_malloc