PHPのPCREについて、基本的なこと
PCREとPCRE-JITとPHPの話Ryo TomidokoroPHP Study 2019/05/29@hanhan1978
View Slide
そもそも何の話か?
preg_… の関数群[引用] PHP PCRE - Manualhttps://www.php.net/manual/en/book.pcre.php
正規表現を使っている例
preg系関数群の正規表現の処理で使われているライブラリ(PCRE)について、特に運用時に気になる設定とか、パフォーマンスに関係する部分について話します。※書き方とか文法の話はしません。
PCREとは?
Perl Compatible Regular ExpressionPerl互換正規表現
と言われても、ピンと来ない人が多いと思うので、とても簡単な正規表現の説明※詳しくは英語版wikipediaを読むと分かりやすい。日本語でも大体OK
50's - 形式言語の研究過程でスティーヴン・クリーネが文字群の数学的表記を記述60's - ケン・トンプソンがQEDエディタに文字列のパターンマッチ方法として導入その後 - edエディタや、grepへ同様機能が実装され、正規表現として認知されていった。ざっくりとした歴史
Basic Regular Expression正規表現の種類Extended Regular ExpressionGNU Emacs Regular Expressionsed, awk, grep の正規表現| + ? などの表現が追加PHPではereg_系の関数で使われる正規表現(現在は非推奨)\w \b などの特殊文字列表現が使える
正規表現の種類Perl Compatible Regular ExpressionLazy match (非欲張り量指定子)などの強力で柔軟な表現力が加わった正規表現におけるデファクトスタンダードのような存在
非欲張り量指定子Non GreedyとかLazyとか言われるhoge正規表現は基本欲張 hoge非欲張り量指定子なら hoge
で、PCREとは?
Perl互換正規表現ライブラリのnative実装。独自のAPIとPOSIX準拠のAPIの両方がある。BSDライセンスで公開されていて、様々な言語に取り入れられている。
[引用] https://www.pcre.org/
PCRE-JITとは?
Just In Time Compiler正規表現のパターンマッチの速度を上げる目的で導入された正規表現のJITコンパイラ通常の正規表現実行前に、JIT最適化を行うためCPUに余分な負荷をかけるが、正規表現マッチングのパフォーマンスが上がる。
正規表現ライブラリのベンチマーク[引用] 正規表現技術入門 - 新屋良磨、鈴木勇介、高田謙技術評論社 (p99)
正規表現の最適化を行う分、プロセッサに余計な負荷をかけるが、正規表現のパターンマッチ実行時間を大幅に軽減できる。特殊な理由が無い限りPCRE-JITの利用が望ましい。
PHPとPCRE
PCREとPCRE-JITの歴史1997-9-10 ver 0.91 -> PCREのもっとも古いchangelogの日付...2011-10-21 ver 8.20 -> PCRE-JITがリリース2015-1-05 ver 10.00 -> PCRE2がリリースこのリリース以降、PCREはバグフィックスのみ。PCRE-JITもPCRE2-JITとバージョンに合わせた名前に変更※重要PCRE2-JITにおいて、APIに若干の変更がある[引用] https://www.pcre.org/original/changelog.txt
PHPとPCRE-JITの歴史PCREはPHP4系から存在していたようなので馴れ初めは割愛[2015-12-03] PHP7のリリース、pcre.jit=1 がデフォルト設定に。[2017-11-30] PHP7.2 Support for PCRE JIT Fast Path -> pcre_jit_exec[2018-12-06] PHP7.3のリリース PCREからPCRE2にライブラリが変更[引用] https://www.php.net/releases/
PHPとPCREの関係PHPのソースコード内に丸っとPCREのソースコードが同梱されている。
PHP7.2系
PHP7.3系
実際にベンチマークしてみる
PHP7以降はpcre.jitが有効
pcre.jit最強?実行時間を考えるとpcre.jitを有効化すべきだが、場合によっては有効化できない場合がある。PHP5系において、コンパイル後の正規表現の実行スタックサイズが64Kを超えてしまう場合、pcre.jitでエラーが発生する。PREG_JIT_STACKLIMIT_ERROR
PREG_JIT_STACKLIMIT_ERRORの解決策としてpcre.jit を0に設定するという対策があるがオススメはしない。先程みたとおり、jitコンパイラで最適化された正規表現はとにかく速い。
JIT無効でベンチマークしてみる
pcre2の性能差で7.3系はjit無効化しても多少性能が良い
pcre.jitを無効化する場合は、その代償をよく考えた上で無効化することせめてPHP7.3系に上げることで、JIT無効化環境でもPHP5系よりも速いパフォーマンスを手に入れることは可能。
ところで
64Kを突破したくないですか?
JITのStack Sizeはmin 32K[引用] https://www.pcre.org/original/doc/html/pcrejit.html
もう少しよく読むと...
最大値は任意に指定できる[引用] https://www.pcre.org/original/doc/html/pcrejit.html
PCRE2も同様
Stackサイズ表PHPバージョン PCRE-JITスタックサイズ7.0系 32K ~ 64K7.1系 32K ~ 64K7.2系 32K ~ 64K7.3系 32K ~ 192K7.3系で PCRE_JIT_STACK_MAX_SIZE が変更になっていた
エラー内容とその対応注)良い子は真似しちゃ駄目な対応が含まれます。
1. PHP7.3系にあげてみる2. 複雑な正規表現を改善する3. スタックサイズを上げるext/pcre/php_pcre.c#define PCRE_JIT_STACK_MAX_SIZE (192 * 1024)を書き換えてコンパイルPREG_JIT_STACKLIMIT_ERROR
1. PCREのコンパイルオプションを変えるext/pcre/pcre2lib/config.h#define LINK_SIZE 2これを4とかにする。64K -> 128K までコンパイル済み正規表現のサイズが上がる。Compilation failed: regularexpression is too large
まとめPHP7.3系からPCRE2に変更PHP7.3系はStackサイズがちょっと大きいpcre.jit=0はパフォーマンスが悪い最終手段は再コンパイル!