PHPの ctype_digit() と preg_match() の処理速度の比較

kusanoctype_digit, PHP, preg_match

初投稿となります、クライゼルグループの kusano です。

何を書こうか考えたのですが、PHP の文字列チェック関数について少し気になったことがあったので、この機会に簡単な調査をしてみました。

PHP で文字列がどういった文字で構成されているかを確認する関数として有名なのは preg_match でしょうか。正規表現によるマッチングを行えるので万能ですね。

一方、文字列が「0 から 9 の数字」のみで構成されているかどうかのみをチェックする代わりに速い (らしい) 関数として ctype_digit といったものもあると聞きました。

以下では、ctype_digit 関数とはそもそもどういった関数かをまとめた上で、preg_match との速度比較を行ってみようと思います。

ctype_digit とは

ctype_digit 関数の仕様については こちら に書いてある通りで、「与えられた文字列 text のすべての文字が数字であるかどうかを調べます。」となります。

ただ、リンク先ページの「注意」にも書いてあるとおり、気をつけるべき点があります。以下に引用します。

  • 注意:
    • この関数を活用するには string を渡さなければなりません。 たとえば integer を渡すと、期待する結果にならない可能性があります。 HTML フォームに入力された整数値は、integer ではなく string 型で返されます。 マニュアルの 型 についての節を参照ください。
    • -128 から 255 までの整数値を渡すと、ひとつの文字の ASCII 値とみなします (負の値には 256 を足して、拡張 ASCII の範囲に収まるようにします)。 それ以外の整数値は、10 進数を含む文字列とみなします。

つまり、-128 から 255 までの整数値を ctype_digit に渡してしまうと、文字列が 0 から 9 の数字のみで構成されているかどうかではなく、その ASCII 値に対応する文字列が 0 から 9 の数字かどうか、がチェックされるというわけですね。

実際に簡単なプログラムを動かして ctype_digit の振る舞いを見てみます。

<?php

for ($i = -512; $i <= 512; $i++) {
    if ($i < 256 and $i > -129) {
        echo "ASCII Code: ";
        echo chr($i);
    } else {
        echo "Non ASCII Code";
    }
    
    echo " ($i): ";
    var_dump ( ctype_digit($i) );
}

上記のコードを実行すると、以下のような結果が出ます。 (長いので一部割愛します)


Non ASCII Code (-512): bool(false)   ← ASCII 値とは扱わないが 
Non ASCII Code (-511): bool(false)      "-" が含まれるので false
             :
Non ASCII Code (-129): bool(false)
ASCII Code: <80> (-128): bool(false) ← ASCII 値として扱い、
ASCII Code: <81> (-127): bool(false)    0-9 の文字ではないので false
             :
ASCII Code:  (-2): bool(false)
ASCII Code:  (-1): bool(false)
ASCII Code: ^@ (0): bool(false)
             :
ASCII Code:   (32): bool(false)
ASCII Code: ! (33): bool(false)
             :
ASCII Code: . (46): bool(false)
ASCII Code: / (47): bool(false)
ASCII Code: 0 (48): bool(true)  ← ASCII 値として扱い、
             :                     0-9 の文字なので true
ASCII Code: 1 (49): bool(true)
ASCII Code: 9 (57): bool(true)
ASCII Code: : (58): bool(false) ← ASCII 値として扱い、
ASCII Code: ; (59): bool(false)    0-9 の文字ではないので false
ASCII Code: < (60): bool(false)
             :
ASCII Code:  (254): bool(false)
ASCII Code:  (255): bool(false)
Non ASCII Code (256): bool(true)  ← ASCII 値と扱わず、0-9 の文字だけで
Non ASCII Code (257): bool(true)     構成されると見なされ true
             :

実際にこういったプログラムを書いてみると、どういった動きになるかひと目で分かりますね。

なお、float 型や bool 型はどの値を与えても false が返ります。

したがって、ctype_digit を利用する場合、あえて「ASCII 値に対応する文字列が 0 から 9 の数字かどうかをチェック」するのでない限り、値を必ず string 型にしてから渡すようにするよう注意が必要ということですね。

ちなみに、preg_match に string 型以外を渡すと、string 型にキャストして判定されるようです。

ctype_digit と preg_match との速度比較

ctype_digit と preg_match との速度比較 としては、ctype digit と preg matchでの文字列比較速度 で比較検証がされていますね。こちらの結果によるとほとんど両者の速度は変わらないそうです。

ただ、こちらで紹介している方法ですと、それぞれの関数による処理時間に加え、以下の処理時間も計測に含まれているようです。

  • 文字列の生成処理
  • echo による出力処理

仮に上の 2 つの処理が ctype_digit と preg_match に比べて数倍以上時間がかかってるとすると、2つの関数の処理時間の差が埋もれてしまっているかもしれませんね。

そんなわけで、ctype digit と preg matchでの文字列比較速度 で紹介されているコードから、echo による出力を省き、文字列生成と文字列チェックのそれぞれの処理の前後に microtime 関数を入れて、それぞれにかかる時間を計測できるようにしたコードで改めて計測を行ってみました。

検証に使用したコード

【string_generator2.php】

<?php

function generate_random_strings($type='letters')
{
    switch($type){
      case 'digits':
        $asset = '0123456789';
        break;
      case 'letters':
      default:
        $asset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        break;
    }

    $length = strlen($asset);
    $max = rand(4, 32);
    $result = array();
    for($i = 0; $i < $max; $i++){
        array_push($result, $asset[rand(0, $length-1)]); 
    }

    return implode($result);
}

function generate_list($max=100, $type='letters')
{
  $start_time = microtime(true);
  $result = array();
  for($i = 0; $i < $max; $i++){
    array_push($result, generate_random_strings($type));
  }
  echo "str_gen:\t";
  echo microtime(true) - $start_time . " sec\n";
  return $result;
}

function generate_letters_list($max=100)
{
  return generate_list($max);
}

function generate_digits_list($max=100)
{
  return generate_list($max, 'digits');
}

【ctype_digit_bench.php】

<?php

require_once('string_generator2.php');

function validate($variables)
{
  $start_time = microtime(true);
  foreach($variables as $v){
    if(ctype_digit($v)){
      $valid++;
    }
    else{
      $invalid++;
    }
  }

  echo "ctype_digit:\t";
  echo microtime(true) - $start_time . " sec\n";

  echo "# valid:   $valid\n";
  echo "# invalid: $invalid\n";
}

validate(generate_letters_list(100000));
validate(generate_digits_list(100000));

【preg_match_bench.php】

<?php

require_once('string_generator2.php');

function validate($variables)
{
  $start_time = microtime(true);
  foreach($variables as $v){
    if(preg_match('/^[0-9]+$/', $v)){
      $valid++;
    }
    else{
      $invalid++;
    }
  }

  echo "preg_match:\t";
  echo microtime(true) - $start_time . " sec\n";

  echo "# valid:   $valid\n";
  echo "# invalid: $invalid\n";
}

validate(generate_letters_list(100000));
validate(generate_digits_list(100000));

上記のコードによる計測内容は要約すると以下のようになります。こちらの 1,2 をそれぞれ時間計測しています。

  1. 10万 x 2 行のランダム文字列を生成(generate_letters_list および generate_digits_list 関数)し、validate 関数に渡す
  2. validate 関数内で、それぞれ ctype_digit と preg_match で文字列の検査を実施

検証した環境

上記のコードを、以下の環境で 10 回実施し、その平均値をとってみました。

  • OS : CentOS 6.2
  • CPU : Intel Core2Duo 3.33GHz
  • Memory : 512MB
  • PHP : Ver.5.3.3

検証結果

  • ctype_digit_bench.php
    • A-Z, a-z, 0-9 で構成されたランダム文字列 10 万行を生成・チェック
      • 文字列生成: 6.28 秒
      • ctype_digit: 0.175 秒
    • 0-9 で構成されたランダム文字列 10 万行を生成・チェック
      • 文字列生成: 6.29 秒
      • ctype_digit: 0.170 秒
  • preg_match_bench.php
    • A-Z, a-z, 0-9 で構成されたランダム文字列 10 万行を生成・チェック
      • 文字列生成: 6.32 秒
      • preg_match: 0.274 秒
    • 0-9 で構成されたランダム文字列 10 万行を生成・チェック
      • 文字列生成: 6.46 秒
      • preg_match: 0.289 秒

この結果から、以下のように考えられるのではないかと思われます。

  • 文字列生成は文字列チェック (ctype_digit と preg_match) に比べ、10 倍以上時間がかかる
  • ctype_digit の方が preg_match より 1.5 倍程度速い

参考にさせていただいたサイトでの検証ですと、前者の点で ctype_digit と preg_match の差が文字列生成側の誤差の範囲内になってしまっていたのかもしれませんね。

ctype_digit の方が単機能なので当たり前といえば当たり前ですが、一応これで ctype_digit と preg_match との正味のチェックの処理時間の差が確認できたかと思います。

まとめ

計測の結果、ctype_digit は preg_match の 1.5 倍程度速いようでした。ただ、preg_match のように int 型で渡した場合に string 型と同じようには判定してくれませんので、利用する際は注意が必要になりますね。

以上、今更ネタかもしれませんが、何かのお役に立てれば幸いです。

kusanoctype_digit, PHP, preg_match

Posted by kusano