がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

array_* と foreachの処理時間検証

なにかでちょろっと気になったのでざっくり検証したので、軽くlog残し。
基本的に「foreachに有利な処理*1」なので、まぁ参考程度に見ていただければ。
ただ言い方を変えると「だから処理によっては(「配列を2回ぶん回す」事になるから)array_*使うと処理的には重くなるからその辺は勘案してね」って内容ではあります。

「元が1万要素ある配列」に対して
・奇数だけを抜き出して
・値を倍にする
処理をぶんまわしてみました。

先に計測結果。
10回うごかした時間と、最後に使用メモリ*2

// array関数版(無名関数)
0.0016610622406006sec
0.0012879371643066sec
0.0011820793151855sec
0.0011670589447021sec
0.0011560916900635sec
0.0011970996856689sec
0.0011940002441406sec
0.0012528896331787sec
0.0010929107666016sec
0.0011200904846191sec
2097152

// array関数版(動的関数)
0.002579927444458sec
0.0016429424285889sec
0.0014491081237793sec
0.0013840198516846sec
0.0014119148254395sec
0.0015451908111572sec
0.0015830993652344sec
0.0015368461608887sec
0.0014801025390625sec
0.0014641284942627sec
2097152

// foreach版(動的関数)
0.00094008445739746sec
0.00081992149353027sec
0.00080585479736328sec
0.0007941722869873sec
0.00082993507385254sec
0.00081396102905273sec
0.00085997581481934sec
0.00083804130554199sec
0.00079202651977539sec
0.00078392028808594sec
2097152

// foreach版(内部べた書き)
0.00034594535827637sec
0.00023603439331055sec
0.00034213066101074sec
0.00025701522827148sec
0.00025200843811035sec
0.00022697448730469sec
0.00030303001403809sec
0.00024294853210449sec
0.00026392936706543sec
0.0002589225769043sec
2097152

おおまかに「array関数使うんなら無名関数のほうが早いっぽい*3」「foreachなら中にベタっと処理書く方が早い」。
トータルとしては「array関数よりforeachのほうが早い」(今回の処理の場合)。
array関数の無名関数については「想像してないわけじゃなかったけどでも一定の気づきがある」結果で、foreachについてはまぁ「そうだろうなぁ」くらい(関数のcall、コスト0ではないしねぇ)。

実行時間については、一番重い「array関数版(動的関数)」と一番軽い「foreach版(内部べた書き)」で、大体、7.5倍くらいの実行時間の差異があるのかな。違うっちゃぁ違うし、誤差っちゃぁ誤差だし、くらいの微妙な感じ。
「array関数版(無名関数)」と「foreach版(内部べた書き)」で、5倍弱、くらい。

なのでまぁ「エライこと巨大な配列を扱う」んなら或いは一考する要素かもしれないし、そうでなければ「誤差レベル」って気もする。

そうすると後は「今までにやってきた技術背景次第」なのかなぁ、とも。
PythonとかRubyとか、あと最近のJavaScript辺りなんかだとarray関数版的な書き方を元々しているので、そちらからの系譜の人達は、割合とarray関数版的が「読みやすい、わかりやすい」って言って好む傾向があるように思います。

おいちゃんは元々がN88-BASICからZ80aのマシン語C言語経由で(他にいくつかあるけど)PHPに来てるので、foreachで十分に使いやすいのでforeachでいいかなぁ、くらい。
(C++iteratorが割と色々と好き……あの「autoが使えなかった頃の死ぬるほど長い型名」含めてwww)

その辺を前提に、あとはまぁ各自お好みで(笑

最後に、確認したコードは以下の通りです。

<?php

// 元ネタ作成
$awk = range(1, 10000);

// やりたい処理の関数その1:奇数偶数判定
function a(int $i) : bool
{
    return 1 === ($i & 1);
}
// やりたい処理関数その2:値を倍にする
function b(int $i) : int
{
    return $i * 2;
}

// array関数版(無名関数)
function t1(array $awk) : array
{
    return array_map(function($i) {
        //return $i * 2;
        return $i;
    }, array_filter($awk, function($i) {
        return (1 === ($i & 1));
    })
    );
}

// array関数版(動的関数)
function t2(array $awk) : array
{
    return array_map('b', array_filter($awk, 'a'));
}

// foreach版(動的関数)
function t3(array $awk) : array
{
    $ret = [];
    foreach($awk as $v) {
        if (true === a($v)) {
            $ret[] = b($v);
        }
    }
    return $ret;
}

// foreach版(内部べた書き)
function t4(array $awk) : array
{
    $ret = [];
    foreach($awk as $v) {
        if (1 === ($v & 1)) {
            $ret[] = $v * 2;
        }
    }
    return $ret;
}

// 計測
for($i = 0; $i < 10; ++$i) {
    $t = microtime(true);
    t4($awk); // ここ、書き換える
    $t_end = microtime(true);
    $t = $t_end - $t;
    echo $t , "sec\n";
}
// 使用メモリ
echo memory_get_peak_usage(true) , "\n";

*1:array_*だと、確実に「配列を2回ぶん回す」事になるので、処理的に不利になる

*2:使用メモリは変わらなかったから、計測しなくてもよかったなぁ……とはいえまぁそれがわかるって意味では必要だったのかも

*3:コンパイル的にインライン展開とかしてるのかね?