Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

⾃自⼰己紹介 •  @chobi_̲e  (ちょびえ) •  職業:Web系でアプリケーション/インフ ラエンジニア •  専⾨門:PHP,  MySQLあたり広く浅く •  OSS:  PECL  ProtocolBuffers,  Sundown,   php-‐‑‒git2,  fluent-‐‑‒logger-‐‑‒php等

Slide 3

Slide 3 text

本⽇日のお題 ⼊入⾨門Zend  Memory  Manager 略略してZendMM 今⽇日はZend  Memory  Managerを読み始めるにあたって 必要な事柄をおさらいしつつ、Zend  MMのさわりのとこ ろまで読み進めてみます。

Slide 4

Slide 4 text

Zend  MMを調べるかに⾄至るまで •  Reactを始めとしたライブラリの台頭 •  PECL  event等のevent系拡張の出現 •  daemonや⼤大規模なバッチ処理理の実⾏行行 PHPを使ってシングルスレッドで⼤大量量にコネクションを処理理 したり、⼤大量量のデータを扱う事がより⼀一般化してきた。しか しそれと同時に⾊色々課題も⾒見見つかってきた ・GCの範囲はどこまでか正確に知る必要が出てきた ・PHPの変数がどこまで⽣生きていけるか知る必要が出てきた なぜ私が

Slide 5

Slide 5 text

新しい仕組みにより出てきた課題 •  PECL  ThreadによりPHPプロセス内で新 しくThreadを作成し、PHPVMが実⾏行行でき るようになった – マルチスレッド化の道筋が⾒見見えたが extension実装者側でPHPの変数がどのコン テキストまで⽣生きられるか知る必要が出てき た – というかマルチスレッドで実⾏行行するために zend_̲vmの知識識も必要になった

Slide 6

Slide 6 text

新しい仕組みにより出てきた課題 •  Reactなどの台頭によりPHPでWebServer を書くことも増えたが同時にメモリリー クに深刻に悩まされるようになった – 俺の実装が悪いのかお前の実装が悪いのかも はや判断がつかない •  PHPで書いたサーバーのどこがボトルネッ クか判断できる必要がでてきた – なんか遅いなー、きっとgcのせいじゃない? 知らないけどきっとそう。【本当に?】

Slide 7

Slide 7 text

本題に⼊入る前に いろいろなおさらい

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

いろいろなおさらい SAPI

Slide 10

Slide 10 text

おさらい:  SAPI •  いろんなサーバーやアプリケーションに 組み込みやすいようにServer  APIという のがある •  cli,  apache,  fpm等全て別のSAPIとして 実装 •  モジュール初期化、リクエスト初期化、 などのライフサイクルの概念念がある 詳しくはdo_akiさんの相模原PHP  vol1での発表を参照ください
 h.p://d.hatena.ne.jp/do_aki/20140124/1390520996  

Slide 11

Slide 11 text

おさらい:  Apache  SAPI •  所謂mod_̲php •  listen,  phpの初期化してfork – 基本MINITは親でしか呼ばれない – RINIT/RSHUTDOWNがリクエスト毎に⼦子プ ロセスで繰り返される(使いまわされる) – MaxRequestsPerChild超えるとkillして回収

Slide 12

Slide 12 text

いろいろなおさらい PHPの変数について

Slide 13

Slide 13 text

おさらい:  変数 •  $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;

Slide 14

Slide 14 text

おさらい:  変数 (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'

Slide 15

Slide 15 text

おさらい:  変数 (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であることを意味する  

Slide 16

Slide 16 text

簡単なまとめ:  変数 •  Cで書かれているので当然内部の型でPHP の変数等を表現している •  zval単体で表現できるのは数値と⽂文字列列 配列列やobjectの表現は別途HashTableや Objectが必要となる (普段はこの知識識必要ないけどThread使うときは必要) •  きっとhnwさんがzvalについて詳しくあとで解説してくれるはず

Slide 17

Slide 17 text

いろいろなおさらい Garbage  Collection

Slide 18

Slide 18 text

おさらい:  Garbage  Collection •  Reference  Count⽅方式(従来) – 内部的に参照数を保持し0になったらfree – 循環参照に対応出来ない •  Concurrent  Cycle  Collection  in   Reference  Counted  System(>=5.3) – 内部バッファにルートを貯めてMarkSweap – 循環参照に対応 – 詳しくはBacon01Concurrentでググろう PHPのGCはこの2種類の組み合わせ

Slide 19

Slide 19 text

PHPでよく⾒見見かけるリーク •  PHP5.2以下を運⽤用する場合よくメモリ リークしてた – PHP内部,  extensionのメモリ割り当て/開 放の不不備 – ユーザーコード実装者の経験不不⾜足 •  バッチ処理理でselect  *  from〜~とかで死亡 •  循環参照しまくりでメモリ利利⽤用量量がもりもり •  そもそも不不要なデータを開放する概念念がない⼈人

Slide 20

Slide 20 text

メモリリーク事例例: •  PHPでHTTP  Serverを書いた –  コネクションハンドリング周りで永遠にクライア ント関連の不不要なデータを参照し続けリクエスト が増えるたびにメモリ利利⽤用量量がもりもり増えて最 終的に殺される –  Callback地獄でどれを開放していいか設計者もわ からない増体で書いてしまい、メモリ利利⽤用量量が (ry •  PHPでバッチ処理理を書いた –  MySQL等から全データ取得するAPIをコールして データ取り過ぎで殺される –  複雑すぎるデータ構造で開放出来なかった

Slide 21

Slide 21 text

簡単なまとめ:  GC •  最近のPHPでリーク(してるように⾒見見える)の は⼤大抵ユーザーのコード、設計が悪い –  昔はinternal側の実装で発⽣生したリークは確かに多 かった •  Full  GCではなくConcurrent  GCなのでふんわり 性能が落落ちる。zend.enable_̲gcで無効にでき る。当然循環参照系の解放ができなくなるので unset地獄となる。使わないなら使わないでき ちんと設計考えて書こう。 PHPを使う理理由の⼤大半が細かいことを気にせずに楽したい、という のだと思うので迂回⽅方法があるならそっち使うのがオススメ

Slide 22

Slide 22 text

多分眠くなったと思うので •  デモ –  libuv  extensionを使ったHTTP  Serverのベンチ マーク –  libuv  extensionとwebsocket  frame  extension を使ったWebsocket実装の確認 https://github.com/chobie/php-‐‑‒uv https://github.com/chobie/php-‐‑‒ websocketframe ※こういう実際のアプリケーションではないベンチマークとかあまり役に ⽴立立たないので気にしないように

Slide 23

Slide 23 text

どうでしたか? PHPやればできるこ (てきとーにやるにはすげー楽)

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Zend  MMの⽬目的 •  メモリ割り当ての効率率率化 – より速いメモリ管理理 •  mallocのコール回数の抑制 –  コンテキストスイッチの削減 –  フラグメンテーションの抑制 –  CPU利利⽤用率率率の削減 – より少ないメモリ割り当て •  フラグメンテーションが少なくなるから結果的に少なくなる •  cacheによるメモリの再利利⽤用 – 安全なメモリ管理理 ZendMMを使うことでPHP実⾏行行中のメモリ利利⽤用量量の詳細が簡単に把握できる。ま た管理理を⾃自前で⾏行行うことにより割り当てはより⾼高速に⾏行行うことができる

Slide 26

Slide 26 text

Zend  MMのレイヤ Zend  Memory  Manager Libc  malloc   mmap custom kernel Physical  Memory Zend  MMはメモリ管理理の抽象レイヤある為、⾃自分の使いたい メモリアロケーターも組み込むことができる (jpauliの図を参考)

Slide 27

Slide 27 text

例例えば ex.php

Slide 28

Slide 28 text

例例えば 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無効にし よう、と安直に考えずきちんとパフォーマンス取ってから考えた⽅方がいい。迂回⽅方法は いくらでもある)

Slide 29

Slide 29 text

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;  

Slide 30

Slide 30 text

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にデバッグ表示が書いてあるので有効にすれば
 一覧で出せる

Slide 31

Slide 31 text

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 を扱う(メモリ管理の対象ではない)

Slide 32

Slide 32 text

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が試している)

Slide 33

Slide 33 text

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等の別の⽅方法を使う必要がある)

Slide 34

Slide 34 text

ZendMMに関連するPHP  API (jpauliのunderstanding  php  memoryより拝借) memory_get_usage(true  /  false)の違い

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

メモリが割り当てられるまで •  渡されたサイズの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にして読み進めるのが良い

Slide 37

Slide 37 text

まとめ •  どの仕組みがどの領域なのか理解すること によって問題発生時の調査が早くできるよう になる •  正しい知識を得て自分たちのワークロードに あった解決方法を考えられるようになろう

Slide 38

Slide 38 text

その他 ・[参考]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