PHPのイテレーションの話とか

一時期PHPで飯を食っていた身*1として、軽く突っ込みを。

4. if文は「===」を使う
PHP勉強会で、Dinoのhnwさんに教わったことですが、'0.00' == '0'は真です。
もし、仮に、パスワード処理でデータ側でMD5などのハッシュ化してない場合に、上記が真になっては非常にまずいのではないでしょうか。
さらにいえば、もっといろいろな形で、真となる場合があるので「==」は使わず、「===」を使いましょう。

    if ($str === 'hoge') {
    if (! ($str === 'hoge')) {
    if (is_null($null) === false) {

書いてあること自体はまったくそのとおりなのだが、例がちょっと変。普通に以下のように書けばいい。

    if ($str !== 'hoge') {
    if (!is_null($null)) {

真偽値を返す関数の結果は比較しないほうが見やすいんじゃないかな。PHP には and, or, xor はある癖に not が無いし unless もないので可読性が少し悪いが。

5. countは配列数を毎回数えてる

これを知ったのは、PukiwkiPukiWiki のクリーンアップの記事を見たときなのですが。
countは配列の件数を数えるらしいので次のような書き方に直したほうが無難です。

    // if(count($array))
   if(! empty($array))
   // for($i = 0; $i < count($array); $i++)
   $count = count($array);
   for($i = 0; $i < $count; $i++)

なんか count が配列の要素数に対して線形時間かかるみたいな書き方だが、そのような事実はない
元のPukiwikiのクリーンアップのほうの話は empty との比較の話なので、そっちのコードも一緒にベンチマーク

% cat bench.php
require_once 'Benchmark/Timer.php';

function test_count($array, $iterate) {
    for ($i = 0; $i < $iterate; $i++) {
        count($array);
    }
}

function test_empty($array, $iterate) {
    for ($i = 0; $i < $iterate; $i++) {
        empty($array);
    }
}

$a = range(1, 10);
$b = range(1, 10000);

$timer = new Benchmark_Timer();
$timer->start();
test_count($a, 1000000);
$timer->setMarker('count10');
test_count($b, 1000000);
$timer->setMarker('count10000');
test_empty($b, 1000000);
$timer->setMarker('empty');
$timer->stop();
$timer->display();
?>

% php bench.php
---------------------------------------------------------
marker       time index            ex time         perct
---------------------------------------------------------
Start        1161924627.81220400   -                0.00%
---------------------------------------------------------
count10      1161924629.24052200   1.428318        36.81%
---------------------------------------------------------
count10000   1161924630.64644100   1.405919        36.23%
---------------------------------------------------------
empty        1161924631.69277700   1.046336        26.96%
---------------------------------------------------------
Stop         1161924631.69281400   0.000037         0.00%
---------------------------------------------------------
total        -                     3.880610       100.00%
---------------------------------------------------------

素数に100倍の差があっても、コストは一緒。empty も早いとはいえ性能向上は30%程度なので、よほど繰り返されるわけでなければ、わざわざcountから置換するほどのことも無い。
そもそも、PHP連想配列と配列を区別できないので、インデックスが0から順に始まっているとは限らない訳で、素直に foreach 使っておいたほうがいいんじゃないかな。連想配列でも要素の順序は保存されているし。
どうせなので、ループの場合のベンチマークも。

<?php
require_once 'Benchmark/Timer.php';

function test_count_loop($array, $iterate) {
    for ($j = 0; $j < $iterate; $j++) {
        $n = 0;
        $count = count($array);
        for ($i = 0; $i < $count; $i++) {
            $n += $array[$i];
        }
    }
}

function test_foreach($array, $iterate) {
    for ($j = 0; $j < $iterate; $j++) {
        $n = 0;
        foreach ($array as $value) {
            $n += $value;
        }
    }
}

function test_foreach_with_key($array, $iterate) {
    for ($j = 0; $j < $iterate; $j++) {
        $n = 0;
        foreach ($array as $i => $value) {
            $n += $value;
        }
    }
}

$a = range(0, 9999);

$timer = new Benchmark_Timer;
$timer->start();
test_count_loop($a, 100);
$timer->setMarker('count_loop');
test_foreach($a, 100);
$timer->setMarker('foreach');
test_foreach_with_key($a, 100);
$timer->setMarker('foreach_with_key');
$timer->stop();
$timer->display();
?>

PHP 4.4.2 CLIの場合。

---------------------------------------------------------------
marker             time index            ex time         perct
---------------------------------------------------------------
Start              1161927045.52569000   -                0.00%
---------------------------------------------------------------
count_loop         1161927046.91695700   1.391267        34.92%
---------------------------------------------------------------
foreach            1161927047.63913600   0.722179        18.12%
---------------------------------------------------------------
foreach_with_key   1161927049.51007500   1.870939        46.96%
---------------------------------------------------------------
Stop               1161927049.51014100   0.000066         0.00%
---------------------------------------------------------------
total              -                     3.984451       100.00%
---------------------------------------------------------------

PHP 5.1.2 CLI の場合。

---------------------------------------------------------------
marker             time index            ex time         perct
---------------------------------------------------------------
Start              1161927023.92807000   -                0.00%
---------------------------------------------------------------
count_loop         1161927024.26778700   0.339717        37.76%
---------------------------------------------------------------
foreach            1161927024.50450100   0.236714        26.31%
---------------------------------------------------------------
foreach_with_key   1161927024.82780600   0.323305        35.93%
---------------------------------------------------------------
Stop               1161927024.82786100   0.000055         0.01%
---------------------------------------------------------------
total              -                     0.899791       100.00%
---------------------------------------------------------------

あれ、同じマシンで実行したのに随分性能が違うなぁ。まぁ、どっちにしても、単純ループなら key なしの foreach が最速ってことで。

*1:といっても学生のアルバイトだが