結論
- 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良さそう



