LoginSignup
10
7

More than 5 years have passed since last update.

複数のデリミタによるexplodeは何が速いか

Last updated at Posted at 2018-02-05

結論

  • PHP7以降はpreg_splitが高速化。優位に。
  • PHP5.6以前はmb_splitが高速。PHP7での大きな変動は見られない。
  • 置換を使ったexplodeはどのバージョンでも比較的安定したよい速度が出る。

前書き

どうも。xdebugで調査するとデータベース操作とフレームワークの起動が支配的で小手先の速度改善が虚しくなってきたものです。

投稿時期を待っている間にタイムリーなコメント
PHPで少しでも速いコードを書きたい時のためのメモ - コメント - Qiita
がありましたでの置いておきます。

この手の話はまっとうですし、折りに触れ目につきます。
しかしms単位であっても速い処理を探すのは単純に楽しい…1
『納得』は全てに優先するぜッ!!と言う言葉もあるので難しいのですが、のめり込み過ぎず、やり過ぎず。

そして素人のたいていのベンチマークは何かしら不備があるものなので。
私のこの記事自体もやはり「納得」「選択」のための一資料としてお付き合いするべきだと思いました。

概要

explode関数はデリミタをひとつしか受け付けない。
より柔軟な分割関数にはpreg_splitmb_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();

結果

2017-12-07_13h27_09-min.png

2017-12-07_13h28_26-min.png

mb_splitは稀に速度が低下し、今回の最新7.2は悪い方が出てしまったようだ。
5.6まではpreg_splitmb_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();

結果

2017-12-07_14h05_57-min.png

2017-12-07_14h06_29-min.png

えーと、何がしたかったのか自分でもわからない。
explodeが最速なことは当然として、preg_splitが全バージョンで比較的速い。
explodeより速いものがあったのは流石に誤差として、
そもそもexplodeが一桁ms~二桁msの大きめなゆらぎがあることが気になる。

あると良かった検証

デリミタなしのときのstr_split含めた検証

multiexplodeの注意点?

'multiexplode'はデリミタがstringの一文字でもなんとか動くが、これが複数文字やマルチバイトの場合、$delimiters[0]で置換してしまうので、一つでも配列に入れておこう。…タイプヒンティング

まとめ?

今回も多用な値でテストをしていないので純粋に信じてはいけない。
また、鬼車とPCREも置いておき。

カリカリせず、PHP7なら何も考えずに全てpreg_splitでいいかもしれない。
一文字の正規表現パターンが気持ち悪いなら単純にexplodepreg_splitの併用でも良い。

5.6以前ならexplodemb_splitの併用がわかりやすいか。

  • PHPバージョン毎の(性能改善)差異に振り回されなくない
  • 複数の単純なデリミタが頻発する(正規表現を使わない)
  • 性能がいい*_splitと既に使っている正規表現関数(preg_match, mb_ereg_replaceなど)が逆になってしまう

なんてときはmultiexplodeも視野に入るのでは。

性能を気にするなら当然できるところはexplodeで行う。

なお、個人的にはpreg_splitで使える$flag引数にも注目しておきたい。(とくにPREG_SPLIT_NO_EMPTY)

explodeに限りませんが、
この場合はどっちでもいけるけどこっち~
と何らかの指針が無いと、そのとき見つけたコードで実装してしまい全体の統一感が失われることもあるので、注意したいですね。速度もですが。
explodeとsplitですでに統一感が無い

ともあれ再帰関数は不要でしょう。

参考


PREG_SPLIT_NO_EMPTY良さそう


  1. ゆえに「パフォーマンス改善が目的の仕事」なら自制しなければならない 

  2. かなり目からウロコ。 

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7