結論
- PHP7以降は
preg_split
が高速化。優位に。 - PHP5.6以前は
mb_split
が高速。PHP7での大きな変動は見られない。 - 置換を使った
explode
はどのバージョンでも比較的安定したよい速度が出る。
前書き
どうも。xdebugで調査するとデータベース操作とフレームワークの起動が支配的で小手先の速度改善が虚しくなってきたものです。
投稿時期を待っている間にタイムリーなコメント
PHPで少しでも速いコードを書きたい時のためのメモ - コメント - Qiita
がありましたでの置いておきます。
この手の話はまっとうですし、折りに触れ目につきます。
しかしms単位であっても速い処理を探すのは単純に楽しい…1
『納得』は全てに優先するぜッ!!と言う言葉もあるので難しいのですが、のめり込み過ぎず、やり過ぎず。
そして素人のたいていのベンチマークは何かしら不備があるものなので。
私のこの記事自体もやはり「納得」「選択」のための一資料としてお付き合いするべきだと思いました。
概要
explode
関数はデリミタをひとつしか受け付けない。
より柔軟な分割関数にはpreg_split
とmb_split
があるが、どちらも正規表現を使う。
確かに「AあるいはBをデリミタとする」は正規表現の範疇だが、一般的に正規表現は遅いと言われる。
一方、explode
のユーザーノートにはデリミタを配列で受け取れる関数の作り方が紹介されている。 2
複雑な正規表現の力を必要としない単純なデリミタ、単純な対象Stringの場合、どれが速いか検証してみた。
合わせて、古くから受け継がれて使われている再帰式explode
の性能も調査した。
調査環境
デリミタ、分割対象はmultiexplode
で使われていた
array(",",".","|",":")
"here is a sample: this text, and this will be exploded. this also | this one too :)"
を使用。
コードを動かす場所として
3v4l thx 3v4lでPHPの動作検証 - Qiita
で複数バージョンをチェック。
実行時間の計測には
lavoiesl/php-benchmark: Tool to compare different functions in PHP
を使用。
各クラスファイルをコピペで3v4lに貼り付けて動かした。
テストコード
/*
php-benchmarkのクラス記述省略
*/
$benchmark = new Benchmark;
function multiexplode ($delimiters,$string)
{
$ready = str_replace($delimiters, $delimiters[0], $string);
$launch = explode($delimiters[0], $ready);
return $launch;
}
function homemadeexplode($splitter, $str)
{
$return = [];
if (is_array($splitter) == true) {
foreach ($splitter as $sp) {
$str = homemadeexplode($sp, $str);
}
$return = $str;
} else {
if (is_array($str) == true) {
foreach ($str as $st) {
$tmp = explode($splitter, $st);
$return = array_merge($return, $tmp);
}
} else {
$return = explode($splitter, $str);
}
}
return $return;
}
$text = "here is a sample: this text, and this will be exploded. this also | this one too :)";
$benchmark->add('multiexplode', function() use($text) { return multiexplode( [",",".","|",":"], $text); });
$benchmark->add('homemadeExplode', function() use($text) { return homemadeexplode([",",".","|",":"], $text); });
$benchmark->add('preg_split', function() use($text) { return preg_split( '/[,.|:]/', $text); });
$benchmark->add('mb_split', function() use($text) { return mb_split( '[,.|:]', $text); });
$benchmark->setCount(50000);
$benchmark->run();
結果
mb_split
は稀に速度が低下し、今回の最新7.2は悪い方が出てしまったようだ。
5.6まではpreg_split
はmb_split
と比べて明らかに遅かったが、7.0から高速化し逆転。
multiexplode
はほぼ安定して2位を維持している。
テキストにマルチバイトを含んでも変わらないもよう。
https://3v4l.org/4Cec9#output
追加:デリミタが一つの場合は?
コード
通常のexplodeを追加した。
$text = "here is a sample: this text, and this will be exploded. this also | this one too :)";
$benchmark->add('multiexplode', function() use($text) { return multiexplode( [","], $text); });
$benchmark->add('homemadeExplode', function() use($text) { return homemadeexplode([","], $text); });
$benchmark->add('preg_split', function() use($text) { return preg_split( '/,/', $text); });
$benchmark->add('mb_split', function() use($text) { return mb_split( ',', $text); });
$benchmark->add('explode', function() use($text) { return explode( ',', $text); });
$benchmark->setCount(50000);
$benchmark->run();
結果
えーと、何がしたかったのか自分でもわからない。
explode
が最速なことは当然として、preg_split
が全バージョンで比較的速い。
explode
より速いものがあったのは流石に誤差として、
そもそもexplode
が一桁ms~二桁msの大きめなゆらぎがあることが気になる。
あると良かった検証
デリミタなしのときのstr_split
含めた検証
multiexplodeの注意点?
'multiexplode'はデリミタがstringの一文字でもなんとか動くが、これが複数文字やマルチバイトの場合、$delimiters[0]
で置換してしまうので、一つでも配列に入れておこう。…タイプヒンティング
まとめ?
今回はも多用な値でテストをしていないので純粋に信じてはいけない。
また、鬼車とPCREも置いておき。
カリカリせず、PHP7なら何も考えずに全てpreg_split
でいいかもしれない。
一文字の正規表現パターンが気持ち悪いなら単純にexplode
とpreg_split
の併用でも良い。
5.6以前ならexplode
とmb_split
の併用がわかりやすいか。
- PHPバージョン毎の(性能改善)差異に振り回されなくない
- 複数の単純なデリミタが頻発する(正規表現を使わない)
- 性能がいい
*_split
と既に使っている正規表現関数(preg_match
,mb_ereg_replace
など)が逆になってしまう
なんてときはmultiexplode
も視野に入るのでは。
性能を気にするなら当然できるところはexplode
で行う。
なお、個人的にはpreg_split
で使える$flag
引数にも注目しておきたい。(とくにPREG_SPLIT_NO_EMPTY
)
explode
に限りませんが、
この場合はどっちでもいけるけどこっち~
と何らかの指針が無いと、そのとき見つけたコードで実装してしまい全体の統一感が失われることもあるので、注意したいですね。速度もですが。
explodeとsplitですでに統一感が無い
ともあれ再帰関数は不要でしょう。
参考
- PHP: explode - Manual
- PHP: preg_split - Manual
- PHP: mb_split - Manual
- php - preg_split vs mb_split - Stack Overflow
PREG_SPLIT_NO_EMPTY
良さそう