PHPの最新状況:PHP 8.0リリース間近(第14回)
2020年いよいよ終わりが近づき,PHP 8.0のリリースも間近に迫ってきました.本稿執筆時点ではリリース候補版の最終版にあたるリリース候補4版が11月12日に予定通りリリースされており,正式版リリースは計画通り11月26日の予定です.
この連載では,これまで数回に渡りPHP 8の新機能について紹介してきました.今回は,前回までに紹介した内容の一部更新と,その他の変更点について紹介します.
JITオプションの変更
JIT(Just-In-Time)コンパイル機能は,PHP 8における目玉の機能であり,リリースに向けて継続的に機能強化や調整が図られています.PHP8で導入されるJIT機能は,キャッシュ用ZendエクステンションであるOPcacheの機能の一部として実装されています.OPcacheをインストールした後,以下のように設定ファイル(php.ini)に指定することで,JIT機能が使用可能となります.
zend_extension = opcache
opcache.enable = 1
opcache.jit = on
opcache.jit_buffer_size = 128M
最初の行はZendエクステンションとしてOPcacheを読み込むコマンドです.2,3行目はそれぞれOPcache,JIT機能を有効にします.4行目はJIT用バッファメモリサイズを指定するもので,この例では128MBを指定しています.なお,適切な設定値は搭載メモリの量などにより変わるため,調整が必要です.デフォルトではゼロとなっているため,何らかの値の指定が必要です.コマンドライン版PHP(CLI)でベンチマークを行う際には,OPcacheを有効にするため,以下のオプションも指定します.
opcache.enable_cli = 1
JITの動作モードを指定するには,opcache.jitオプションに4桁の数字を指定します.4桁のオプションはCRTOと表され,CはCPU固有の最適化,Rはレジスタ配置,TはJITのトリガー,Oは最適化レベルを表します.標準的なオプションは,従来1205となっており,opcache.jit=onの代わりに以下のように指定していました.
opcache.jit = 1205
この場合,CPU固有の最適化としてAVX命令生成が行われ,レジスタ配置としてグローバル線形探索,JITはスクリプトロード時に行われます.この際,静的型推論と内部処理解析に基づく最適化が行われます.
一般にJITはコンパイルによるオーバーヘッドを伴うため,場合によっては実行がかえって遅くなってしまう場合もあります.このため,JITでは何らかの手段でスクリプトの実行時にプロファイリングにより時間を要する部分を見つけ出し,部分的にコンパイルを行う手法が用いられます.
最近,PHPにおいてもトレーシングJITと呼ばれるプロファイリングに基づくJIT適用の仕組みの導入が検討され,実装されました.トレーシングJITを適用した場合,実行時に処理が複数回行われる場所(HOTなコード)を動的に見つけ出し,該当する部分をトレースして,コンパイル対象にします.この際のオプションは1254に相当します.トレーシングJITはJITのデフォルト動作モードとなっており,以下のようにJITを有効にすると自動的に選択されます.
opcache.jit = on
また,以下のようにトレーシングJITを明示的に指定することも可能です.
opcache.jit = tracing
なお,従来のデフォルトのJIT動作モードはfunction JITとして定義されており,以下のように指定できます.
opcache.jit = function
この指定は,opcache,jit = 1205と等価です.
以下,PHP8におけるこの他の変更点について紹介します.
名前付き引数
PHP8では,関数の引数を「パラメータ名:値」のように名前タグで指定することができようになります.例えば以下のコードを考えてみましょう.
array_fill(0, 100, 50);
名前付き引数で記述すると例えば以下のようになります.
array_fill(value:50, num: 100, start_index:0);
各引数の意味が分かりやすくなり,順番の任意に変えられるようになります.なお,順序に基づく従来の引数との併用も可能ですが,順序に基づく引数の適用は名前付き引数より前の引数に限定されます.
引数を省略すると各引数のデフォルト値が適用されます.例えば,以下のコードを考えてみましょう.
htmlspecialchars($s,ENT_COMPAT|ENT_HTML401,’UTF-8’,false)
名前付き引数を適用すると,例えば,以下のように記述できます.
htmlspecialchars($s,double_encode:false)
第4引数にfalseを指定したい場合,従来はそれ以外の引数の指定を省略することはできませんでしたが,名前付き引数を適用すると当該引数のみ指定できるため,簡潔に記述できます.
コンストラクタによるプロパティ設定自動化
PHP 8.0では,クラスのコンストラクタでプロパティの自動指定ができるようになります.以下のコードを見てみましょう.
class Foo {<br> public float $x;<br> public float $y;<br> public function __construct(float $x=0.0,float $y=0.0) {<br> $this->x=$x; $this->y=$y;<br> }<br>}
このコードでは,コンストラクタでプロパティ($x,$y)の値を指定しています.
PHP8で導入された機能を利用するとこのコードを以下のようにより簡潔に記述できます.
class Foo {<br> public function __construct(public float $x=0.0,public float $y=0.0) {}<br>}
コンストラクタで,new Foo(1.0,2.0)のように記述すると,プロパティ$x,$yが自動的に作成され,それぞれ1.0,2.0が代入されます.
数値の定義明確化
PHP 8では、文字列中に記述した数値の定義の明確化が行われます.Webアプリケーションからのデータは一般に文字列として入力されますが,PHPでは文字列の内容により自動的に数値として認識します.以下の簡単な例を見てみましょう.
var_dump(is_numeric("123abc")); // bool(false)<br>var_dump(is_numeric("123 ")); // bool(false)<br>var_dump(is_numeric(" 123")); // bool(true)<br>var_dump(is_numeric(" 123 ")); // bool(false)
PHP 7.4で実行すると,各行の出力はそれぞれfalse,false,true,falseとなり,3行目のみ数値として認識されていることがわかります.しかし,なぜ数値の前にのみ空白がある場合に数値として認識され,数値の後ろや前後に空白がある場合には数値として認識されるのかについては,仕様の一貫性がないと言えます.
PHP 8で同じコードを実行すると,各行の出力はfalse,true,true,trueとなります.
var_dump(is_numeric("123abc")); // bool(false)<br>var_dump(is_numeric("123 ")); // bool(true)<br>var_dump(is_numeric(" 123")); // bool(true)<br>var_dump(is_numeric(" 123 ")); // bool(true)
文字列の前後の空白の有無によらず一貫して数値として認識されていることがわかります.この変更は下位互換性がない変更であり,場合によっては不具合の種となる可能性があります.しかし,実際には従来の動作に依存するコードはほとんどないと思われ,副作用はないものと思われます.