More Related Content Similar to PHP と SAPI と ZendEngine3 と
Similar to PHP と SAPI と ZendEngine3 と (20) PHP と SAPI と ZendEngine3 と5. 目次
1. Server API (SAPI)
2. Zend Thread Safety (ZTS)
3. Zend Engine
3.1 Virtual Machine (PHP VM)
3.2 Memory Allocator & Garbage Collector
9. 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)
起動して実行
10. 複数ある 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]
11. 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
15. SAPI と SAPI module
PHP コア
(Zend Engine)
SAPI
module
SAPI
様々な
Web
Server
Web
Server
ごとの違
いを吸収
PHP
Scri
pt
異な
る通
信方
式に
対応
PHPコアを起動するための
ブートストラップでもある
16. Server API (SAPI)
• Web サーバごとの違いを吸収して、様々な Webサーバで
PHP 実行環境を利用可能にする仕組み
• これを実装した SAPI module が PHP 実行環境のブートス
トラップとなる
– 以前は Web サーバごとに数多く存在していたが、現在は Web
サーバ - アプリケーションサーバ間のプロトコルが共通化して
きたため少数
– 現在は、ほとんど動作方式の違いでしかない
– SAPI module のことを指して SAPI と呼ぶこともある
17. 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
18. 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)
19. 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…
20. SAPI module の役割ではないこと
• ネットワーク接続
– socket 接続
– DB 接続
• ファイル入出力
– ファイルシステムへのアクセス
• プロセス間通信
– signal
– pipe (shell_exec)
多くは Zend Engine のストリームAPI (一部は拡張が直接制御)
21. SAPI module による出力制御
<?php
header("HTTP/1.0 418 I'm a tea pot");
echo "Hello World!";
Hello World! Hello World!
• apache2handler (mod_php)
の場合は apache httpd の関
数(ap_rwrite等) 呼び出し
• fpm-fcgi (fpm) の場合は
FastCGI protocol による
socket への書き込み
• cli の場合は write
system call の呼び出し
(header 出力は無視)
PHP Script
22. 設定ファイル(php.ini) のデフォルトパス
• SAPI module ごとに異なる
– コンパイル時に指定できる
• ディストリビューションのパッケージで提供されているもの
はだいたい一貫してる
• コンパイル済みのバイナリを利用する場合、それぞれ異なる
php.ini ファイルを参照する可能性があるので注意
– cli と php-fpm とで違う php.ini を見ているとかありうる
23. 単一バイナリに 複数の 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)
24. 同じ 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
25. 利用している 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
26. SAPI まとめ
• PHP 実行環境は同じバージョンでも複数の実体 (バ
イナリ) がある
• 実体の違いのほとんどは SAPI module の違い
• SAPI module の違いによって、PHP Script の実行
の仕方や、出力方法等が異なる
• ただし バイナリの違い !== SAPI の違い であるこ
とに注意
30. 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)時 にリセットされる
31. 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 の基本的
な実行モデル
において、
一つのプロセ
スで、同時に
複数のリクエ
ストを処理す
ることはでき
ない
32. 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
リクエスト
を並列処理
するために
複数のプロ
セスを待機
させておく
33. 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プロセスが処理する最大リクエスト数
35. 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 でないと破綻するので注意
36. 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)
37. ZTS まとめ
• PHP 実行環境は同じバージョンでも複数の実体があり、
さらに、TS版(ZTS有効) と NTS版(ZTS無効) がある
• ZTS はスレッド化されたリクエストを処理するための機能
• PHP は 通常、 マルチプロセスによる並列化を想定している
ので、特別な理由がない限りは NTS版を利用しよう
• PHP Script での マルチスレッドプログラミングは避けた
ほうが良い (ほかの言語使おう)
40. PHP 全体像
• SAPI module や Zend Engine が Extension とし
て関数やクラスを登録
• PHP Script は登録された関数やクラスを介して、あ
るいは、 Zend Engine が言語構造として提供する機
能を介してのみ、外の世界とつながることができる
• 起点は常に SAPI module
• この大枠は、PHP5(ZE2) から PHP7(ZE3) になって
も変化していない
41. 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互換用)
42. 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
43. 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
44. 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() 関数で参照可能
• おそらく、バージョン番号にあまり意味はないのでは
46. PHP Script は 中間コード に
変換されてから実行される
<?php
$name = "php";
echo "hello {$name}";
$_main: ; (lines=5, args=0, vars=1, tmps=2)
; (before optimizer)
; hello.php:1-5
L0 (3): ASSIGN CV0($name) string("php")
L1 (4): NOP
L2 (4): T2 = FAST_CONCAT string("hello ") CV0($name)
L3 (4): ECHO T2
L4 (5): RETURN int(1)
hello.php
php -d opcache.opt_debug_level=0x10000 -d opcache.enable_cli hello.php
1行1行が opcode という中間コード
(一連のopcode は op_array という塊で管理される)
47. phpdbg での出力例
(表現の仕方が異なるだけで同じもの)
<?php
$name = "php";
echo "hello {$name}";
function name: (null)
L1-5 {main}() hello.php - 5 ops
L3 #0 ASSIGN $name "php"
L4 #1 NOP
L4 #2 FAST_CONCAT "hello " $name ~1
L4 #3 ECHO ~1
L5 #4 RETURN<-1> 1
hello.php
phpdbg -p* hello.php
48. PHP
Compiler & VM
PHP Script
Opcode
Request
Output
Compiler
Lexing
Parsing
Compilation
VM
Execution
[INCLUDE_OR_EVAL]
requireやinclude,
eval を実行すると、そ
のタイミングでコンパイ
ルされる
(例えばオートロード時)
PHP Script
50. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
51. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
52. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
53. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
ECHO int(2)
NOP
ECHO int(3)
RETURN int(1)
ECHO int(4)
RETURN null
f.php
foo()
54. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
ECHO int(2)
NOP
ECHO int(3)
RETURN int(1)
ECHO int(4)
RETURN null
f.php
foo()
55. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
ECHO int(2)
NOP
ECHO int(3)
RETURN int(1)
ECHO int(4)
RETURN null
f.php
foo()
56. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
ECHO int(2)
NOP
ECHO int(3)
RETURN int(1)
ECHO int(4)
RETURN null
f.php
foo()
57. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
ECHO int(2)
NOP
ECHO int(3)
RETURN int(1)
ECHO int(4)
RETURN null
f.php
foo()
58. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
ECHO int(2)
NOP
ECHO int(3)
RETURN int(1)
ECHO int(4)
RETURN null
f.php
foo()
59. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
ECHO int(2)
NOP
ECHO int(3)
RETURN int(1)
ECHO int(4)
RETURN null
f.php
foo()
60. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
ECHO int(2)
NOP
ECHO int(3)
RETURN int(1)
ECHO int(4)
RETURN null
f.php
foo()
61. 実行例
<?php
echo 1;
require 'f.php';
foo();
echo 5;
<?php
echo 2;
function foo() {
echo 4;
}
echo 3;
run.php
f.php
ECHO int(1)
INCLUDE_OR_EVAL (require) string("f.php")
INIT_FCALL_BY_NAME 0 string("foo")
DO_FCALL_BY_NAME
ECHO int(5)
RETURN int(1)
run.php
ECHO int(2)
NOP
ECHO int(3)
RETURN int(1)
ECHO int(4)
RETURN null
f.php
foo()
62. コンパイル時 と 実行時
• コンパイル時に解決されること
– 定数計算 (PHP>=7.0)
– ::class (クラスが存在するかどうか関係ない)
– __LINE__ 等のマジック定数 (ただし trait 内の __CLASS__
は実行時に決定)
– トップレベルの関数・クラス定義 (early binding)
• 実行時に解決されること
– その他すべて
64. early binding なし
<?php
func(); // Call to undefined function func()
new Cls(); // Class 'Cls' not found
if (1) {
function func() {}
class Cls {}
}
func(); // OK
new Cls(); // OK
定義がトップレベルではないの
で early binding されない
65. Compiler & VM
PHP Script
Opcode
Request
Output
Compiler
Lexing
Parsing
Compilation
VM
Execution
[INCLUDE_OR_EVAL]
requireやinclude,
eval を実行すると、そ
のタイミングでコンパイ
ルされる
(例えばオートロード時)
PHP Script
通常リクエスト終
了時に破棄される
(opcache はこれ
を再利用)
66. 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までだと思う)
67. Threading Model
• threaded code と呼ばれるコードを生成する手段
– (multi-thread とは関係ない言葉であることに注意)
– PHP においては opcode の処理の仕方 (分岐方式)
• 4つの中から選択することができる (自前でPHPをコンパイルする場合)
– SWITCH (シンプル、ただし遅い)
– CALL (7.1 までのデフォルト)
– GOTO (速い、ただし一部の環境では動かない?)
– HYBRID (CALL をベースに GOTO も利用 7.2 からのデフォルト)
HYBRID はマイクロベンチで1.5倍、一般的なアプリケーション
でも数パーセント速度向上するとのこと
68. 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.sklzend_vm_def.h
VM の本体である zend_execute 関数が
ある (.h だけど実装が含まれる)
opcode の実装 テンプレート
zend_vm_execute.h
69. zend_vm_def.h
• 各 opcode の実装がここに記述されている
– ex:ZEND_JMP
• 実行する opcode の位置を変更するだけ
– ex:ZEND_CLONE
• object で __clone があれば呼び出し、なければ組み込みのコピー処
理を実行
• opcode によって実行コストが全く違う
– 単純に "opcode が少ない" === "速い" というわけではない
70. ZEND_VM_INTERRUPT
• 7.1 で導入された、割込み処理を低コストで実現する仕組み
– opcode を処理するたびに EG(vm_interrupt) を確認
– 割込み処理は非同期で EG(vm_interrupt) を 1 にする
– 割込みがあれば zend_interrupt_function や
zend_timeout が実行される
• signal や timeout (max_execution_time) で利用
• 割込み処理が早くなっただけでなく、タイムアウト時の挙動
が従来よりも安定している
72. PHP のメモリ管理
• PHP7になったときに大幅に変更 (高速化の主因)
– zval (変数の内部表現) の扱いとメモリレイアウト変更
• よりCPUキャッシュに乗りやすく
– アロケータ (Zend Memory Manager) の刷新
• 基本確保単位が変更
Segment:256KB(PHP5) -> Chunk:2MB(PHP7)
• GC が 参照カウンタ + 循環参照コレクタ というの
は変わらない
73. Zend Memory Manager
(Zend/zend_alloc.c)
• マネージャという名前だけど、実質的にはメモリアロケータ
• 通常割り当てられたメモリはすべてリクエスト終了時に破棄
– ZMM により、リクエストを越えてリークする可能性はない
– ただし、永続的な割り当ては管轄外(システムアロケータに委譲)
• メモリ解放漏れのチェック機構
– PHP Script の組み方によってリークするケースは循環参照のみ
– 拡張のメモリリークチェックに便利
• memory_limit の管理も仕事の一つ
– get_memory_usage(false) は 要求されたメモリ量
– get_memory_usage(true) は 実際に確保したメモリ量
74. Zend Memory Manager (PHP7)
<?php
$str = "PHP Script";
emalloc()
zend_mm_heap chunk chunk chunk
zend_mm_mmap()
mmap or VitualAlloc
(>=2MB)
page
page
page
page
OS
chunk
単位で確保slot
slot
slot
75. Zend Memory Manager (PHP5)
OS
<?php
$str = "PHP Script";
emalloc()
zend_mm_heap segment segment segment
win32 mmap_zeromalloc mmap_anon
storage_handler (ZEND_MM_MEM_TYPEで変更可)
(>=256KB / ZEND_MM_SEG_SIZEで変更可)
segment
単位で確保
block block block
76. 廃止されたZMM関連の環境変数
(PHP5 では利用可能だったが PHP7では使えない)
• ZEND_MM_SEG_SIZE
– メモリ確保の基本単位(default:256KB)
– ある程度大きくすることで、メモリのフラグメントが防げるはず
• ZEND_MM_MEM_TYPE
– storage handler の指定
– 指定が無効な場合は、有効な名前を列挙してプログラム終了
• ZEND_MM_COMPACT (WIN32環境にのみ影響)
– メモリコンパクションを発動するサイズ (default:2MB)
– mmap系ではコンパクション実装がダミーになっているので意味がない
77. 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 ミス軽減による高速化を期待できる
79. 参照カウント(附記)
• メモリレイアウト変更により 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 以降)
80. 循環参照
$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
解放されない
81. 循環参照コレクタ
(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)
82. 循環参照コレクタの改善
• PHP7.3 で導入予定 (master マージ済み)
– https://github.com/php/php-src/pull/3165
• 大量に object がある場合のGC速度が大幅に改善された
– composer 速くなるかも
– 動作原理は大きく変わってない (と思う)
– ちゃんとは追えてない。詳しい人誰か
• ルートバッファ(リークする可能性のあるオブジェクトを記録したもの)の
最大数が(実質)撤廃
– 従来は 10,000 件を超えて記録できなくなるとリークしてた
– 初期 16,384件 (128KB) から、倍々あるいは 1MB 単位で最大 1GB まで増加
– 双方向リンクドリストからベクタに (これが高速化の主因?)
87. 関数は拡張に属している
• 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 のみ特別に読み込んでいるが、この挙動はちょっと謎)
88. 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"
89. 外から見分ける方法
ライブラリの読み込みに利用するシンボルの違いで判別可能
$ 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 シンボルが存在 (頭に _がつくケースもアリ)
95. 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
Editor's Notes mod_php も embeded も 非Winだと libphp7.so という名前であることが多いのが紛らわしい。
どちらも共有ライブラリで、前者は apache httpd 専用、後者は目的を限定しない
PHP7で一貫した 64bit サポートを実現するにあたって、SAPI module の改修は必須だった。
しかし、SAPI だけでなく、サーバすらまともにメンテされていないものも多く、利用もされてないので労力を減らすために不要なSAPI の移植は断念したと
ほとんどのサーバは FastCGI 話せるから、PHP-FPM (や cgi-fcgi)使えばWebサーバに依存せずに使えるよね ということらしい
8+15=23種 * https://ci.apache.org/projects/httpd/trunk/doxygen/group__APACHE__CORE__PROTO.html
* https://fastcgi-archives.github.io/FastCGI_Specification.html
単一バイナリで複数の SAPI module
php コマンドは通常 cli, -S オプション付きで起動すると cli-server
他の SAPI module を利用
nginx unit (nginx アプリケーションサーバ) は libphp (embed) を利用して、 独自の SAPI module (unit) を実装している php-cgi は、 CGIとしてもFCGI としても動作可能だけど、どちらも cgi-fcgi SAPI
lsphp も似た感じ 一つの リクエストを処理している間、ほかのリクエストを処理することはできない
リクエストをまたいでリソースが共有されない 起動後にプロセスを立ち上げておいて、リクエストが来たら空いているプロセスが順次処理する
一般的な PHP の実行モデル。
PHP がこうするのではなく、 Webサーバにこう動いてもらう という想定。
ただし、 fastcgi の場合は PHP 自身 (cgi-fcgi/fpm-fcgi) がこのような動きをする
PHP Script のリソースがリクエスト単位であることが重要 一部のWebサーバは、リクエストをスレッドで処理する。
そんなケースにも対応したものが ZTS
ZTS (Zend Thread Safety 版) が想定する実行モデル。
Webサーバではリクエストをプロセスではなくスレッドで処理する分、 (特にWin32 で) パフォーマンスが改善されることがある
反面、リクエスト前後でマルチスレッド対応の処理(共有されるメモリ領域を分離して利用するための処理)が入るため、PHPのパフォーマンスは落ちる。 NOP は何もしないopcode。 コンパイラの最適化によって複数の opcode が統合された際にはよくこうなる。 それまでデフォルトだった CALL に変わり、PHP7.2で CALL にGOTO を混合させた HYBRID がデフォルトに
x86、x86_64、PPC64 以外の CPU アーキテクチャではCALL にフォールバックする
https://www.mail-archive.com/internals@lists.php.net/msg91158.html
https://github.com/php/php-src/commit/27e01cd918dd3309571aa3628e6139d436b10e18
(マージされたコミット)
https://github.com/php/php-src/commit/fc927dc263f95b98a1a64124cce6028185ebbd00
(7.2 系で Hybrid がデフォルトになったコミット) PHP Script で C言語をジェネレート Turn safe timeout handling into general interrupt handling ability.
https://github.com/php/php-src/commit/d0460d8f6be04fc9493fc7db99d29168b46f3e72
基本は chunk 単位での確保
もっとも最小の割り当ては chunk をさらに細分した page に含まれる slot 単位となる php script でメモリが必要になった時、 内部では emalloc という関数(マクロ) が呼ばれる
メモリは segment 単位で確保されるが、その際、実際に OS に対してメモリを確保するのは選択可能な storage_handler
segment ZEND_MM_COMPACT は https://bugs.php.net/bug.php?id=41713 への対応によるものらしい 関数定義 と 関数呼び出しが 別の op_array として生成される