徹底検証!PHP最適化Tips

第2回 文字列置換関数の比較とgdbの使い方

この記事を読むのに必要な時間:およそ 5 分

php_strtrでは,実際の文字列の置換処理が行われます。

2670 PHPAPI char *php_strtr(char *str, int len, char *str_from, char *str_to, int trlen)
2671 {
2672     int i;
2673     unsigned char xlat[256];
2674 
2675     if ((trlen < 1) || (len < 1)) {
2676         return str;
2677     }
2678 
2679     for (i = 0; i < 256; xlat[i] = i, i++);
2680 
2681     for (i = 0; i < trlen; i++) {
2682         xlat[(unsigned char) str_from[i]] = str_to[i];
2683     }
2684 
2685     for (i = 0; i < len; i++) {
2686         str[i] = xlat[(unsigned char) str[i]];
2687     }
2688 
2689     return str;
2690 }

引数trlenには2837行目 MIN(Z_STRLEN_PP(from), Z_STRLEN_PP(to)) から置き換え前後の文字列の最短の長さが渡ります。2681行目では,trlenまでのforループなため短い方にあわせて処理されます。2681行目でfromからtoへの変換する文字列のマッピング情報が一文字ずつ登録されます。長さはtrlenまでとなり置き換え前後の文字列長の短い方にあわせて置換され,置き換え前後の文字列は同じ長さしか処理されないこともわかります。

そして,2685行目のループで一文字づつ処理されていき,変換対象の文字であれば先ほどxlatに代入した文字へ置き換わります。

次にstr_replaceについて見ていきます。

str_replaceはext/standard/string.c内に記述されており,PHP_FUNCTION(str_replace) → php_str_replace_common → php_str_replace_in_subject → php_str_to_str_ex と処理されます。

順に説明していきます。

3770 PHP_FUNCTION(str_replace)
3771 {
3772     php_str_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
3773 }

PHP_FUNCTION(str_replace) ではphp_str_replace_commonが呼ばれるだけです。

3700 static void php_str_replace_common(INTERNAL_FUNCTION_PARAMETERS, int case_sensitivity)

php_str_replace_common()では引数のvalidation,処理を行う文字列の配列チェックが行われます。

3584 static void php_str_replace_in_subject(zval *search, zval *replace, zval **subject, zval *result, int case_sensitivity, int *replace_count)

php_str_replace_in_subject()では置換文字列が配列で渡されているかによって処理が振り分けられ,置換処理を行う関数を実行します。

3403 PHPAPI char *php_str_to_str_ex(char *haystack, int length,
3404     char *needle, int needle_len, char *str, int str_len, int  *_new_length, int case_sensitivity, int *repla     ce_count)

php_str_to_str_ex()では実際の置換処理が行われます。

3417                 end = new_str + length;
3418                 for (p = new_str; (r = php_memnstr(p, needle, needle_len,  end)); p = r + needle_len) {
3419                     memcpy(r, str, str_len);
3420                     if (replace_count) {
3421                         (*replace_count)++;
3422                     }
3423                 }

引数に文字列が与えられた処理では,php_memnstrで置き換え対象の文字列の出現する場所を見つけ,memcpyによって文字列を置き換えます。この動作が繰り返し続けられます。

またstr_replaceでは大文字小文字を区別せずに置き換えることができるため,その際には置き換え文字列それぞれがphp_strtolowerで小文字へ変換されたり,置き換え前後の文字列の長さが違う場合には差分の文字列領域を確保するなどの処理が伴います。

置き換え後の文字列の長さを変えて,計測してみましょう。

'abc' を 'ABCDEF' と,先ほどのベンチマークから置き換え後の文字列の長さを3文字増やして再度計測を行ってみます。

kajidai@laputa:~$ php benchmark_strtr2.php
float(0.00085186958313)
kajidai@laputa:~$ php benchmark_strreplace2.php
float(0.000946998596191)

処理が増えたためにわずかに遅い結果となりました。

このようにstr_replaceではパラメータになにを渡すかにより,結果は変わってきます。 ただし,strtrは検索文字と置き換え文字の一方が長い場合は長い部分は無視され,同じ長さの部分のみ処理される特徴があることを考えると,今回の検証では,正規表現が必要なとき以外はpreg_replaceは使わずに,str_replaceを使ったほうがいいと言えます。

著者プロフィール

梶原大輔(かじわらだいすけ)

大学卒業後,ヤフー株式会社に入社し,Yahoo!ビデオキャストなどのサービス開発に従事。

現在はグリー株式会社でバックエンドシステムの開発を担当。

URLhttp://d.hatena.ne.jp/kajidai/