はじめに
前回に引き続き、
今回は文字列置換関数の比較です。またgdbを用いたPHPコードの読み方についても紹介します。
strtr > str_replace > preg_replace の順に速い
この3つの関数は細かな動きに違いはあるものの、
下記のベンチマーク用のコードを用意して、
<?php
$t = microtime(true);
$i = 0;
while($i < 1000) {
$a = strtr('abcdefghijklmn', 'abc', 'ABC');
++$i;
}
$tmp = microtime(true) - $t;
var_dump($tmp);
?>
<?php
$t = microtime(true);
$i = 0;
while ($i < 1000) {
$a = str_replace('abc', 'ABC', 'abcdefghijklmn');
++$i;
}
$tmp = microtime(true) - $t;
var_dump($tmp);
?>
<?php
$t = microtime(true);
$i = 0;
while ($i < 1000) {
$a = preg_replace('/abc/', 'ABC', 'abcdefghijklmn');
++$i;
}
$tmp = microtime(true) - $t;
var_dump($tmp);
?>
$ php benchmark_strtr.php float(0.000890970230103) $ php benchmark_strreplace.php float(0.000858068466187) $ php benchmark_pregreplace.php float(0.00124001502991)
実行結果は上記のようになりました。
strtrとstr_
strtrとstr_
2804 PHP_FUNCTION(strtr)
中略
2825 if (ac == 2) {
2826 php_strtr_array(return_value, Z_STRVAL_PP(str), Z_STRLEN_PP(str), HASH_OF(*from));
2827 } else {
2828 convert_to_string_ex(from);
2829 convert_to_string_ex(to);
2830
2831 ZVAL_STRINGL(return_value, Z_STRVAL_PP(str), Z_STRLEN_PP(str), 1);
2832
2833 php_strtr(Z_STRVAL_P(return_value),
2834 Z_STRLEN_P(return_value),
2835 Z_STRVAL_PP(from),
2836 Z_STRVAL_PP(to),
2837 MIN(Z_STRLEN_PP(from),
2838 Z_STRLEN_PP(to)));
2839 }
2840 }
PHP_
php_
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_
そして、
次にstr_
str_
順に説明していきます。
3770 PHP_FUNCTION(str_replace)
3771 {
3772 php_str_replace_common(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
3773 }
PHP_
3700 static void php_str_replace_common(INTERNAL_FUNCTION_PARAMETERS, int case_sensitivity)
php_
3584 static void php_str_replace_in_subject(zval *search, zval *replace, zval **subject, zval *result, int case_sensitivity, int *replace_count)
php_
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_
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 }
引数に文字列が与えられた処理では、
またstr_
置き換え後の文字列の長さを変えて、
'abc' を 'ABCDEF' と、
kajidai@laputa:~$ php benchmark_strtr2.php float(0.00085186958313) kajidai@laputa:~$ php benchmark_strreplace2.php float(0.000946998596191)
処理が増えたためにわずかに遅い結果となりました。
このようにstr_
gdbを使ってPHPのコードを読む
検証に役立つ方法として、
ブレークポイントを設定してみましょう。あたりまえですがstrtrでは定義されてません。
(gdb) b strtr Function "strtr" not defined.
strtrは PHP_
332 /* PHP-named Zend macro wrappers */
333 #define PHP_FN ZEND_FN
334 #define PHP_MN ZEND_MN
335 #define PHP_NAMED_FUNCTION ZEND_NAMED_FUNCTION
336 #define PHP_FUNCTION ZEND_FUNCTION
337 #define PHP_METHOD ZEND_METHOD
43 #define ZEND_FN(name) zif_##name
44 #define ZEND_MN(name) zim_##name
45 #define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)
46 #define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
47 #define ZEND_METHOD(classname, name) ZEND_NAMED_FUNCTION(ZEND_MN(classname##_##name))
最終的に
(gdb) b zif_strtr Breakpoint 1 at 0x826c000: file /home/kajidai/work/php5-5.2.6/ext/standard/string.c, line 2807.
ソースファイルと行数が分かっていれば下記のように指定することも可能です。
b stfing.c:2807
また、
499 static PHP_METHOD(PDO, prepare)
次に実際の処理を行っているphp_
(gdb) b php_strtr Breakpoint 2 at 0x81fd81c: file /home/kajidai/work/php-5.2.6/ext/standard/string.c, line 2671.
先ほど使ったstrtrのベンチマーク用のプログラムを走らせてみます。
(gdb) run ~/benchmark_strtr.php Starting program: /usr/local/bin/php ~/benchmark_strtr.php [Thread debugging using libthread_db enabled] [New Thread 0xb7c519e0 (LWP 25628)] [Switching to Thread 0xb7c519e0 (LWP 25628)] Breakpoint 1, zif_strtr (ht=3, return_value=0x8489904, return_value_ptr=0x0, this_ptr=0x0, return_value_used=1) at /home/kajidai/work/php-5.2.6/ext/standard/string.c:2805 2805 {
期待通りにzif_
再開させます。
(gdb) c Continuing. Breakpoint 2, php_strtr (str=0x848991c "abcdefghijklmn", len=14, str_from=0x848a6ac "abc", str_to=0x848a6bc "ABC", trlen=3) at /home/kajidai/work/php-5.2.6/ext/standard/string.c:2671 2671 {
php_
php_
(gdb) finish Run till exit from #0 php_strtr (str=0x848991c "abcdefghijklmn", len=14, str_from=0x848a6ac "abc", str_to=0x848a6bc "ABC", trlen=3) at /home/kajidai/work/php-5.2.6/ext/standard/string.c:2671 zif_strtr (ht=3, return_value=0x8489904, return_value_ptr=0x0, this_ptr=0x0, return_value_used=1) at /home/kajidai/work/php-5.2.6/ext/standard/string.c:2840 2840 } Value returned is $1 = 0x848991c "ABCdefghijklmn"
return_
(gdb) p *return_value $2 = {value = {lval = 138975516, dval = 2.9776604101582473e-313, str = {val = 0x848991c "ABCdefghijklmn", len = 14}, ht = 0x848991c, obj = {handle = 138975516, handlers = 0xe}}, refcount = 1, type = 6 '¥006', is_ref = 0 '¥0'}
さらに見やすく出力するためにzval出力用のprintzvを使ってみます。 printzvはphpのソースを展開すると出てくる.gdbinitに定義されています。 .gdbinitをホームへコピーすることで使用できるようになります。
(gdb) printzv return_value [0x08489904] (refcount=1) string(14): "ABCdefghijklmn"
無事に置換後の文字列が確認できました。
まとめ
今回はstrtr(), str_
普段何気なく使っている関数も、