Slide 1

Slide 1 text

PHP と SAPI と ZendEngine3 と 2018/03/09 PHPerKaigi 2018 do_aki

Slide 2

Slide 2 text

@do_aki @do_aki http://do-aki.net/

Slide 3

Slide 3 text

はじめに

Slide 4

Slide 4 text

はじめに

Slide 5

Slide 5 text

目次 1. Server API (SAPI) 2. Zend Thread Safety (ZTS) 3. Zend Engine 3.1 Virtual Machine (PHP VM) 3.2 Memory Allocator & Garbage Collector

Slide 6

Slide 6 text

Server API (SAPI)

Slide 7

Slide 7 text

PHP プログラムの実行とは

Slide 8

Slide 8 text

Webサーバと組み合わせて実行 (Web Server起動 時にロード済み) Web Server run PHP Script HTTP Request Web Server 組み込み型 Web Server CGI Protocol run PHP Script HTTP Request CGI型 起動して実行 Web Server FastCGI Protocol run PHP Script HTTP Request FastCGI型 起動済み

Slide 9

Slide 9 text

PHP 単体で実行 Built-in WebServer (ex:php –S 127.0.0.1:3000) run PHP Script Webサーバと組み合わせずにスタンドアロンで動作 (Built-in Web Server は IO 多重化による、シングルプロセス実装なのでIO が Busy に なると並列処理できない。もし PHP Script 実行時にクラッシュすると、WebServer も巻 き込んで死ぬのであくまで開発用) HTTP Request Command Execution run PHP Script コマンド実行 (ex: php hoge.php) 起動して実行

Slide 10

Slide 10 text

複数ある PHP の実体(バイナリ) 主なファイル名 説明 [SAPI] libphp7.so php7apache2_4.dll Apache httpd のモジュールとして実行される PHP(ラ イブラリ) いわゆる mod_php [apache2handler] php-cgi php-cgi.exe いわゆる CGI版 PHP。CGIプロトコルだけでなくFastCGI プロトコルも実装した PHP (実行ファイル) [cgi-fcgi] php-fpm 高負荷環境に対応したプロセス管理を備えた、FastCGI プロトコル実装の PHP (実行ファイル) [fpm-fcgi] php php.exe php コマンド。 built in web server もこれで起動で きる (実行ファイル) [cli] [cli-server] phpdbg phpdbg.exe PHP の組み込みデバッガ (実行ファイル) [phpdbg] libphp7.so php7embed.lib 他のアプリケーションに組み込んで PHP を実行すること ができるようになるライブラリ [embed] lsphp LiteSpeed の LSAPI を実装した PHP (実行ファイル) [litespeed]

Slide 11

Slide 11 text

SAPI と SAPI module PHP コア (Zend Engine) SAPI module SAPI Web Server or Shell apache2 handler or php-fpm or cli or etc… PHP Scri pt

Slide 12

Slide 12 text

mod_php の場合 PHP コア (Zend Engine) SAPI module SAPI Apache httpd apache2 handler PHP Scri pt

Slide 13

Slide 13 text

php-fpm の場合 PHP コア (Zend Engine) SAPI module SAPI FastCGI supported Web Server (ex:nginx) fpm- fcgi PHP Scri pt

Slide 14

Slide 14 text

php コマンド の場合 PHP コア (Zend Engine) SAPI module SAPI Shell cli PHP Scri pt

Slide 15

Slide 15 text

SAPI と SAPI module PHP コア (Zend Engine) SAPI module SAPI 様々な Web Server Web Server ごとの違 いを吸収 PHP Scri pt 異な る通 信方 式に 対応 PHPコアを起動するための ブートストラップでもある

Slide 16

Slide 16 text

Server API (SAPI) • Web サーバごとの違いを吸収して、様々な Webサーバで PHP 実行環境を利用可能にする仕組み • これを実装した SAPI module が PHP 実行環境のブートス トラップとなる – 以前は Web サーバごとに数多く存在していたが、現在は Web サーバ - アプリケーションサーバ間のプロトコルが共通化して きたため少数 – 現在は、ほとんど動作方式の違いでしかない – SAPI module のことを指して SAPI と呼ぶこともある

Slide 17

Slide 17 text

SAPI module (PHP7.2) • apache2handler (Apache 2.0 Handler) • cli (Command Line Interface) • cli-server (Built-in HTTP server) • phpdbg (phpdbg) • fpm-fcgi (FPM/FastCGI) • embed (PHP Embedded Library) • cgi-fcgi (CGI/FastCGI) • litespeed (LiteSpeed V6.11) ※括弧は pretty name

Slide 18

Slide 18 text

SAPI module (PHP5.6) apache2handler (Apache 2.0 Handler) / cli (Command Line Interface) / cli-server (Built-in HTTP server) / phpdbg (phpdbg) / fpm-fcgi (FPM/FastCGI) / embed (PHP Embedded Library) / cgi-fcgi (CGI/FastCGI) / litespeed (LiteSpeed V6.10) ↑ PHP7 にもある/↓ PHP7で公式リポジトリからは削除された aolserver (AOLserver) / apache (Apache) / apache_hooks (Apache1) / apache2filter (Apache 2.0 Filter) / isapi (IIS) / caudium (Caudium) / Continuity (Continuity Server Enterprise Edition) / milter (Sendmail Milter SAPI) / nsapi (NSAPI) / phttpd (PHTTPD) / pi3web (PI3WEB) / roxen (Roxen) / thttpd (thttpd) / tux (tux) / webjames (WebJames)

Slide 19

Slide 19 text

SAPI module の役割 • POSTやCookieデータの読み込み (read_post,read_cookie) • 環境変数の読み込み (getenv) • 出力制御 (ub_write,flush) • ヘッダ出力 (header_handler,send_headers, send_header) • 設定ファイル(php.ini)の扱い制御 – デフォルトパスの上書き (php_ini_path_override) – 読み込まない設定 (php_ini_ignore, php_ini_ignore_cwd) etc…

Slide 20

Slide 20 text

SAPI module の役割ではないこと • ネットワーク接続 – socket 接続 – DB 接続 • ファイル入出力 – ファイルシステムへのアクセス • プロセス間通信 – signal – pipe (shell_exec) 多くは Zend Engine のストリームAPI (一部は拡張が直接制御)

Slide 21

Slide 21 text

SAPI module による出力制御

Slide 22

Slide 22 text

設定ファイル(php.ini) のデフォルトパス • SAPI module ごとに異なる – コンパイル時に指定できる • ディストリビューションのパッケージで提供されているもの はだいたい一貫してる • コンパイル済みのバイナリを利用する場合、それぞれ異なる php.ini ファイルを参照する可能性があるので注意 – cli と php-fpm とで違う php.ini を見ているとかありうる

Slide 23

Slide 23 text

単一バイナリに 複数の SAPI module php(.exe) libphp.so php hoge.php php –S 127.0.0.1:3000 cli SAPI cli-server SAPI (--enable-cli) --enable-embed (unit SAPI) そのまま利用 一部利用して実装 [Any App] (embed SAPI)

Slide 24

Slide 24 text

同じ SAPI module で異なる動作 php-cgi lsphp php-cgi hoge.php php-cgi –b 127.0.0.1:9000 CGI として実行 FastCGI (待ち受け) (--enable-cgi) --with-litespeed cgi-fcgi SAPI (内部的には別処理) lsphp hoge.php lsphp –b 127.0.0.1:3000 ファイルを指定して実行 litespeed SAPI (内部的には別処理) LSAPI Server mode

Slide 25

Slide 25 text

利用している SAPI を知る方法 • (実行ファイルの場合) –v オプションを付けて実行 • php_sapi_name() の戻り値 あるいは PHP_SAPI 定数 – 実行しないとわからないけど確実 • phpinfo() の Server API 項目 – ただしこれは SAPI 名ではなく、SAPI の pretty name $ /usr/sbin/php-fpm -v PHP 7.2.2 (fpm-fcgi) (built: Feb 21 2018 08:31:14) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.2.2, Copyright (c) 1999-2018, by Zend Technologies

Slide 26

Slide 26 text

SAPI まとめ • PHP 実行環境は同じバージョンでも複数の実体 (バ イナリ) がある • 実体の違いのほとんどは SAPI module の違い • SAPI module の違いによって、PHP Script の実行 の仕方や、出力方法等が異なる • ただし バイナリの違い !== SAPI の違い であるこ とに注意

Slide 27

Slide 27 text

Zend Thread Safety (ZTS)

Slide 28

Slide 28 text

Q. SAPI module によって 起動した PHPは次に何をする? A. リクエストを待つ リクエスト 受付中

Slide 29

Slide 29 text

Q. SAPI module によって 起動した PHP は次に何をする? A. リクエストを待つ リクエスト 受付中

Slide 30

Slide 30 text

PHP の life cycle SAPI Initialize PHP Module Initialize PHP Module Shutdown (Waiting) Request Initialize Request Shutdown PHP Script Execution SAPI Shutdown Request (実行する PHP File を指定) 基本的にすべてのリソースは、リクエスト終了 (Request Shutdown)時 にリセットされる

Slide 31

Slide 31 text

PHP の life cycle (前頁と同じ内容を別の書き方しただけ) SAPI Initialize SAPI Shutdown PHP module Initialize (MINIT) PHP module Shutdown (MSHUTDOWN) Request Initialize (RINIT) PHP Script Execution Request Initialize (RSHUTDOWN) Request Initialize (RINIT) PHP Script Execution Request Initialize (RSHUTDOWN) t i m e PHP の基本的 な実行モデル において、 一つのプロセ スで、同時に 複数のリクエ ストを処理す ることはでき ない

Slide 32

Slide 32 text

Prefork Model (Multi Process) SAPI Initialize SAPI Shutdown MINIT MSHUTDOWN RINIT PHP Script RSHUTDOWN RINIT PHP Script RSHUTDOWN MINIT MSHUTDOWN RINIT PHP Script RSHUTDOWN MINIT MSHUTDOWN RINIT PHP Script RSHUTDOWN ・・・ process リクエスト を並列処理 するために 複数のプロ セスを待機 させておく

Slide 33

Slide 33 text

Prefork Model (Multi Process) SAPI Initialize SAPI Shutdown MINIT MSHUTDOWN RINIT PHP Script RSHUTDOWN RINIT PHP Script RSHUTDOWN MINIT MSHUTDOWN RINIT PHP Script RSHUTDOWN MINIT MSHUTDOWN RINIT PHP Script RSHUTDOWN ・・・ process リクエスト を並列処理 するために 複数のプロ セスを待機 させておく 並行して起動する最大プロセス数 Apache httpd (mpm:prefork) : MaxClients/MaxRequestWorkers ディレクティブ cgi-fcgi (fcgi) : PHP_FCGI_CHILDREN 環境変数 php-fpm (1poolあたり) : pm.max_children 設定 Apache httpd (mpm:prefork) : MaxRequestsPerChild / MaxConnectionsPerChild ディレクティブ cgi-fcgi (fcgi) : PHP_FCGI_MAX_REQUESTS 環境変数 php-fpm : pm.max_requests 設定 1プロセスが処理する最大リクエスト数

Slide 34

Slide 34 text

一部のWebサーバによる スレッドによるリクエスト処理に対応 (ZTS) SAPI Initialize SAPI Shutdown MINIT MSHUTDOWN RINIT PHP Script RSHUTDOWN RINIT PHP Script RSHUTDOWN RINIT PHP Script RSHUTDOWN RINIT PHP Script RSHUTDOWN thread 単一のプロセス に対し、異なる スレッドでリク エストを並列処 理することが可 能 (その代わり、 マルチスレッド 対応のコストが かかる)

Slide 35

Slide 35 text

ZTS (Zend Thread Safety) • Webサーバがスレッドでリクエストを処理するケースに備えて、 マルチスレッド環境下でもPHPの実行を可能にする特別対応のこと – ZTS を有効にしてコンパイル => TS(Thread Safe) 版 PHP – ZTS を無効にしてコンパイル => NTS (Non Thread Safe) 版 PHP • ZTS は PHP Script 上で マルチスレッドプログラミングをサ ポートするものではない – PHP Script 上での マルチスレッドはやめとけ (by pauli) • https://www.slideshare.net/jpauli/php-and-threads-zts#40 • TS版PHP でも、拡張が Thread Safe でないと破綻するので注意

Slide 36

Slide 36 text

ZTS が有効かどうかを知る方法 • php –v (CLI のみ!) • PHP_ZTS 定数 (int(1): 有効 / int(0): 無効) • phpinfo() の Thread Safety 項目 (enable / disable) $ php –v PHP 7.2.2 (cli) (built: Feb 21 2018 08:30:50) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.2.2, Copyright (c) 1999-2018, by Zend Technologies 有効な場合は (ZTS)

Slide 37

Slide 37 text

ZTS まとめ • PHP 実行環境は同じバージョンでも複数の実体があり、 さらに、TS版(ZTS有効) と NTS版(ZTS無効) がある • ZTS はスレッド化されたリクエストを処理するための機能 • PHP は 通常、 マルチプロセスによる並列化を想定している ので、特別な理由がない限りは NTS版を利用しよう • PHP Script での マルチスレッドプログラミングは避けた ほうが良い (ほかの言語使おう)

Slide 38

Slide 38 text

Zend Engine

Slide 39

Slide 39 text

PHP’s Outline Web Server or Shell SAPI module Zend Engine Extensions PHP Script OS

Slide 40

Slide 40 text

PHP 全体像 • SAPI module や Zend Engine が Extension とし て関数やクラスを登録 • PHP Script は登録された関数やクラスを介して、あ るいは、 Zend Engine が言語構造として提供する機 能を介してのみ、外の世界とつながることができる • 起点は常に SAPI module • この大枠は、PHP5(ZE2) から PHP7(ZE3) になって も変化していない

Slide 41

Slide 41 text

PHP の Source Code 階層 php-src |- TSRM/ .......... (Thread Safe Resource Manager) |- Zend/ .......... (Zend Engine 関連) |- ext/ .......... (各種拡張) |- opcache/ |- standard/ etc… |- main/ .......... (SAPI に共通するロジック群) |- sapi/ .......... (各種 SAPI module) |- apache2handler/ |- cli/ etc… |- win32/ .........(WIN32互換用)

Slide 42

Slide 42 text

Zend Directory の中身 (一部) PHP Script Compiler Lexer (zend_language_scanner.l) Parser (zend_language_parser.y) AST Generator (zend_ast.c) Opcode Generator (zend_compile.c) Memory Management Memory Allocator (zend_alloc.c) Cycles Collector (zend_gc.c) Stream API transport filter wrapper Virtual Machine Def (zend_vm_def.h,zend_vm_execute.skl) Gen (zend_vm_gen.php) Impl(zend_vm_execute.h,zend_execute.c,z end_execute_API.c,zend_vm_opcodes.c,zend_op code.c) Built-in Function (zend_builtin_functions.c) Class (zend_closures.c,zend_gener ators.c) Interface (zend_interfaces.c) Exception (zend_exceptions.c) Data Structures HashTable (zend_hash.c) zend_string (zend_interfaces.c) zend_list.c/zend_llist.c/zend_smart_str.c/zend_s mart_string.c/zend_stack.c/zend_ts_hash.c OOP zend_inheritance.c,zen d_objects.c,zend_objec t_handlers.c,zend_obje cts_API.c Algorithm Sort (zend_sort.c) ini File Parser zend_ini.c,zend_ini_parse r.y,zend_ini_scanner.l Signal Handling zend_signal.c Extension Management zend_extensions.c

Slide 43

Slide 43 text

Zend Engine とは • 主に Zend ディレクトリに含まれるPHPのコア機能 – これが Zend Engine である という定義は(たぶん)ない • PHP Script Compiler と PHP VM が主たる機能といえるだ ろうが、それは他の機能抜きには成り立たないもの – メモリ管理を行う Memory Manager / HashTable, zend_string といっ たデータ構造 / 設定ファイルを読み込むための ini File Parser – etc… • + PHP の基本機能 (を構成する要素) – 拡張管理 / PHP の組み込みクラス、関数、例外 / Stream API / Sort

Slide 44

Slide 44 text

Zend Engine 3 • 大枠は変わらず、個々の機能がブラッシュアップされた – Compiler, VM, Memory Manager,zend_string, HashTable, sort, object handler あたりの改良が主かなと • 最新が PHP 7.2 で Version 3.2 – PHP 7.0 で Version 3.0, PHP 7.1 で Version 3.1 – PHP 5 の時は Version 2.0 – 2.6 • C言語上で ZEND_VERSION 定数として定義(Zend/zend.h) – PHP Script からは zend_version() 関数で参照可能 • おそらく、バージョン番号にあまり意味はないのでは

Slide 45

Slide 45 text

Virtual Machine (PHP VM)

Slide 46

Slide 46 text

PHP Script は 中間コード に 変換されてから実行される

Slide 47

Slide 47 text

phpdbg での出力例 (表現の仕方が異なるだけで同じもの) 1 hello.php phpdbg -p¥* hello.php

Slide 48

Slide 48 text

PHP Compiler & VM PHP Script Opcode Request Output Compiler Lexing Parsing Compilation VM Execution [INCLUDE_OR_EVAL] requireやinclude, eval を実行すると、そ のタイミングでコンパイ ルされる (例えばオートロード時) PHP Script

Slide 49

Slide 49 text

実行例

Slide 50

Slide 50 text

実行例

Slide 51

Slide 51 text

実行例

Slide 52

Slide 52 text

実行例

Slide 53

Slide 53 text

実行例

Slide 54

Slide 54 text

実行例

Slide 55

Slide 55 text

実行例

Slide 56

Slide 56 text

実行例

Slide 57

Slide 57 text

実行例

Slide 58

Slide 58 text

実行例

Slide 59

Slide 59 text

実行例

Slide 60

Slide 60 text

実行例

Slide 61

Slide 61 text

実行例

Slide 62

Slide 62 text

コンパイル時 と 実行時 • コンパイル時に解決されること – 定数計算 (PHP>=7.0) – ::class (クラスが存在するかどうか関係ない) – __LINE__ 等のマジック定数 (ただし trait 内の __CLASS__ は実行時に決定) – トップレベルの関数・クラス定義 (early binding) • 実行時に解決されること – その他すべて

Slide 63

Slide 63 text

early binding

Slide 64

Slide 64 text

early binding なし

Slide 65

Slide 65 text

Compiler & VM PHP Script Opcode Request Output Compiler Lexing Parsing Compilation VM Execution [INCLUDE_OR_EVAL] requireやinclude, eval を実行すると、そ のタイミングでコンパイ ルされる (例えばオートロード時) PHP Script 通常リクエスト終 了時に破棄される (opcache はこれ を再利用)

Slide 66

Slide 66 text

opcode • PHP VM によって実行される命令セット • 3アドレスコード – 命令 + 引数1 + (引数2) + (戻り値格納先) • CV:PHP Script 上の変数 • CONST: 定数(リテラル等) • TMPVAR: PHPスクリプトには現れない一時的な変数 – ex: echo $a + $b + 3 の場合 • 命令の種類は増加傾向にある – PHP5.6 150命令 – PHP7.0 168命令 – PHP7.1 185命令 – PHP7.2 196命令 • count や get_class 等の関数が命令 (ZEND_COUNT, ZEND_GET_CLASS) となることで関数呼び出しのコスト減 ADD $a $b TMP1 ADD TMP1 3 TMP2 ECHO TMP2 (みたいな感じ) (とはいえ、opcode は 内部では unsigned char なので、増えても 255までだと思う)

Slide 67

Slide 67 text

Threading Model • threaded code と呼ばれるコードを生成する手段 – (multi-thread とは関係ない言葉であることに注意) – PHP においては opcode の処理の仕方 (分岐方式) • 4つの中から選択することができる (自前でPHPをコンパイルする場合) – SWITCH (シンプル、ただし遅い) – CALL (7.1 までのデフォルト) – GOTO (速い、ただし一部の環境では動かない?) – HYBRID (CALL をベースに GOTO も利用 7.2 からのデフォルト) HYBRID はマイクロベンチで1.5倍、一般的なアプリケーション でも数パーセント速度向上するとのこと

Slide 68

Slide 68 text

VM の生成方法 $ cd Zend $ php zend_vm_gen.php --with-vm-kind=GOTO zend_vm_opcodes.h generated successfully. zend_vm_opcodes.c generated successfully. zend_vm_execute.h generated successfully. zend_vm_execute.skl zend_vm_def.h VM の本体である zend_execute 関数が ある (.h だけど実装が含まれる) opcode の実装 テンプレート zend_vm_execute.h

Slide 69

Slide 69 text

zend_vm_def.h • 各 opcode の実装がここに記述されている – ex:ZEND_JMP • 実行する opcode の位置を変更するだけ – ex:ZEND_CLONE • object で __clone があれば呼び出し、なければ組み込みのコピー処 理を実行 • opcode によって実行コストが全く違う – 単純に "opcode が少ない" === "速い" というわけではない

Slide 70

Slide 70 text

ZEND_VM_INTERRUPT • 7.1 で導入された、割込み処理を低コストで実現する仕組み – opcode を処理するたびに EG(vm_interrupt) を確認 – 割込み処理は非同期で EG(vm_interrupt) を 1 にする – 割込みがあれば zend_interrupt_function や zend_timeout が実行される • signal や timeout (max_execution_time) で利用 • 割込み処理が早くなっただけでなく、タイムアウト時の挙動 が従来よりも安定している

Slide 71

Slide 71 text

Memory Allocator & Garbage Collector

Slide 72

Slide 72 text

PHP のメモリ管理 • PHP7になったときに大幅に変更 (高速化の主因) – zval (変数の内部表現) の扱いとメモリレイアウト変更 • よりCPUキャッシュに乗りやすく – アロケータ (Zend Memory Manager) の刷新 • 基本確保単位が変更 Segment:256KB(PHP5) -> Chunk:2MB(PHP7) • GC が 参照カウンタ + 循環参照コレクタ というの は変わらない

Slide 73

Slide 73 text

Zend Memory Manager (Zend/zend_alloc.c) • マネージャという名前だけど、実質的にはメモリアロケータ • 通常割り当てられたメモリはすべてリクエスト終了時に破棄 – ZMM により、リクエストを越えてリークする可能性はない – ただし、永続的な割り当ては管轄外(システムアロケータに委譲) • メモリ解放漏れのチェック機構 – PHP Script の組み方によってリークするケースは循環参照のみ – 拡張のメモリリークチェックに便利 • memory_limit の管理も仕事の一つ – get_memory_usage(false) は 要求されたメモリ量 – get_memory_usage(true) は 実際に確保したメモリ量

Slide 74

Slide 74 text

Zend Memory Manager (PHP7) =2MB) page page page page OS chunk 単位で確保 slot slot slot

Slide 75

Slide 75 text

Zend Memory Manager (PHP5) OS =256KB / ZEND_MM_SEG_SIZEで変更可) segment 単位で確保 block block block

Slide 76

Slide 76 text

廃止されたZMM関連の環境変数 (PHP5 では利用可能だったが PHP7では使えない) • ZEND_MM_SEG_SIZE – メモリ確保の基本単位(default:256KB) – ある程度大きくすることで、メモリのフラグメントが防げるはず • ZEND_MM_MEM_TYPE – storage handler の指定 – 指定が無効な場合は、有効な名前を列挙してプログラム終了 • ZEND_MM_COMPACT (WIN32環境にのみ影響) – メモリコンパクションを発動するサイズ (default:2MB) – mmap系ではコンパクション実装がダミーになっているので意味がない

Slide 77

Slide 77 text

ZMM関連の環境変数 (PHP7) • USE_ZEND_ALLOC – PHP5 でも利用可能 – 0 を指定することで、emallocがシステムアロケータをそのまま利用す ることになる – Zend Memory Manager が無効になるので、memory_limit 等も使え なくなる • USE_ZEND_ALLOC_HUGE_PAGES (PHP>=7.0.6) – MAP_HUGETLB マクロが有効な場合のみ利用可能 (実質的に!WIN32) – 0 以外を指定することで、mmap のフラグに MAP_HUGETLB を付与 – (メモリを大量に使うプログラムで) Kernel の Huge Page が有効な らば、TLB ミス軽減による高速化を期待できる

Slide 78

Slide 78 text

参照カウント 解放 'php' (ref:1) $a 'php' (ref:2) $a $b 'php' (ref:1) $a $b 'php' (ref:0) $b $a = 'php'; $b = $a; unset($a); unset($b); 生成

Slide 79

Slide 79 text

参照カウント(附記) • メモリレイアウト変更により PHP7 でカウントの仕方が一部変更になった が、参照カウント方式を利用していることは変わらない そのため、循環参照コレクタも変わらず存在している • 公式のドキュメント「参照カウント法の原理」 は PHP5 の内部をベースと した解説であることに注意 – http://php.net/manual/ja/features.gc.refcounting-basics.php – PHP7 においては、カウントの仕方が若干異なる • The secret of PHP7's Performance を読むとPHP5とPHP7との違いが分 かりやすい (https://www.slideshare.net/laruence/the-secret-of-php7s- performance#28 以降)

Slide 80

Slide 80 text

循環参照 $a = new stdClass(); $b = new stdClass(); $a->r = $b; $b->r = $a; unset($a); unset($b); $a $b stdClass(ref:1) stdClass(ref:1) $a $b stdClass(ref:2) r stdClass(ref:2) r $a $b stdClass(ref:1) r stdClass(ref:2) r $b stdClass(ref:1) r stdClass(ref:1) r 解放されない

Slide 81

Slide 81 text

循環参照コレクタ (Zend/zend_gc.c) • PHP + GC というコンテキストにおいて指すのはだいたいこれ – 参照カウントも GC の一つではあるのだけど、 PHP Script 上でメモ リリークする可能性があるのは循環参照のみだからだと思う • PHP Script 上で実行を制御可能 – gc_enable / gc_diable / gc_enabled / gc_collect_cycles – ちなみに gc_mm_caches (PHP>=7.0)は Zend Memory Manager にお いて空き領域を再利用するための関数 (循環参照コレクタとは無関係) • 循環参照コレクタの動作については y_uti さんの「PHPのGCの 話」が分かりやすい (https://www.slideshare.net/y-uti/php-gc)

Slide 82

Slide 82 text

循環参照コレクタの改善 • PHP7.3 で導入予定 (master マージ済み) – https://github.com/php/php-src/pull/3165 • 大量に object がある場合のGC速度が大幅に改善された – composer 速くなるかも – 動作原理は大きく変わってない (と思う) – ちゃんとは追えてない。詳しい人誰か • ルートバッファ(リークする可能性のあるオブジェクトを記録したもの)の 最大数が(実質)撤廃 – 従来は 10,000 件を超えて記録できなくなるとリークしてた – 初期 16,384件 (128KB) から、倍々あるいは 1MB 単位で最大 1GB まで増加 – 双方向リンクドリストからベクタに (これが高速化の主因?)

Slide 83

Slide 83 text

まとめ • PHP プログラムの実行を支える仕組みの一部を解説しました – PHP7.0 以降もさらに改善が進んでいる • とはいえ、表面の解説をしただけで詳細はもっと深く、また この資料で触れてない仕組みもまだまだたくさんあります • これを機に PHP の内部に興味を持ってくれたら幸い

Slide 84

Slide 84 text

Any Question?

Slide 85

Slide 85 text

(blank)

Slide 86

Slide 86 text

Extensions (Appendix.1)

Slide 87

Slide 87 text

関数は拡張に属している • PHP Script から呼び出し可能な組み込みの関数は、すべてなにか しらの拡張に属している – 拡張の一覧は get_loaded_extensions 関数で取得可能 – 指定した拡張に属する関数の一覧は get_extension_funcs 関数で取 得可能 • Zend Engine が組み込みで提供する拡張 – Core Extension (strlen, define 等 Zend/zend_builtin_functions.c) – Standard Extension (trim, constant 等 ext/standard/basic_functions.c) – 他、 date, pcre, reflection, spl も – (Core のみ特別に読み込んでいるが、この挙動はちょっと謎)

Slide 88

Slide 88 text

2種類の拡張 • Zend Extension – Zend Engine を "拡張" する – PHP Extension としても動作可能 • PHP Extension – PHP Script を "拡張" する – 内部的には PHP Module という名称 • 内部的な違いであって、利用者が意識することは少ない – ini ファイルでの指定の仕方くらい • Zend Extension は "zend_extension=xxx.so" • PHP Extension は "extension=xxx.so"

Slide 89

Slide 89 text

外から見分ける方法 ライブラリの読み込みに利用するシンボルの違いで判別可能 $ nm ―D opcache.so | grep zend_extension_entry 000000000026a700 D zend_extension_entry $ nm -D pdo.so | grep get_module 00000000000059b0 T get_module Zend Extension の場合は必ず zend_extension_entry シンボル が存在 (頭に _がつくケースもアリ) PHP Extension の場合は zend_extension_entry はなく、 get_module シンボルが存在 (頭に _がつくケースもアリ)

Slide 90

Slide 90 text

Compile (Appendix.2)

Slide 91

Slide 91 text

PHP Script

Slide 92

Slide 92 text

字句解析 (Zend/zend_language_scanner.l / re2c)

Slide 93

Slide 93 text

構文解析 (Zend/zend_language_parser.y / bison) T_FUNCTION T_STRING ( ) { } T_ECHO T_ENCAPSED_AND_WHITESPACE ; T_STRING ( ) ; T_OPEN_TAG T_VARIABLE T_VARIABLE function declaration function call

Slide 94

Slide 94 text

AST構築 (Zend/zend_ast.c) generated by https://dooakitestapp.hero kuapp.com/phpast/webapp/

Slide 95

Slide 95 text

Opcodeへの変換 (Zend/zend_compile.c) $_main: ; (lines=5, args=0, vars=0, tmps=1) ; (before optimizer) ; hello.php:1-10 L0 (3): NOP L1 (8): INIT_FCALL 1 112 string("hello") L2 (8): SEND_VAL string("php") 1 L3 (8): DO_UCALL L4 (10): RETURN int(1) hello: ; (lines=5, args=1, vars=1, tmps=1) ; (before optimizer) ; hello.php:3-6 L0 (3): CV0($name) = RECV 1 L1 (4): NOP L2 (4): T1 = FAST_CONCAT string("HELLO ") CV0($name) L3 (4): ECHO T1 L4 (6): RETURN null call hello() declare hello() $ php -d opcache.opt_debug_level=0x10000 -d opcache.enable_cli hello.php