【PHP】配列ループのベンチマーク

PHP

PHPでの配列ループ処理何通りかについてベンチマークテストを実施してみました。

対象のループ処理は、for文、foreach文、while文、array_map()関数などなどです。

対象のPHPバージョンは、8.2 / 8.3 / 8.4 です。

▼テストケース

要素数1,000,000(ひゃくまん)の1次元リスト配列(添え字0スタート連番) $a を用意します。

$a の各要素はランダムな整数とします。

$a について、単純にループを回します。

$a の各要素に1を足した最終結果の配列を返すようにします。

1回のイテレーションで実行時間の計測をします。

▼foreach文

        $b = [];
        foreach ($a as $v) {
            $b[] = $v + 1;
        }
        return $b;

▼for文

        $b = [];
        $count = count($a);
        for ($i = 0; $i < $count; $i++) {
            $b[] = $a[$i] + 1;
        }
        return $b;

▼while文 $i++ で判定

        $b = [];
        $i = 0;
        $count = count($a);
        while ($i < $count) {
            $b[] = $a[$i] + 1;
            $i++;
        }
        return $b;

▼while文 配列ポインタcurrent()/next()を使用

        $b = [];
        while ($v = current($a)) {
            $b[] = $v + 1;
            next($a);
        }
        return $b;

▼while文 イテレータを使用

        $iterator = new ArrayIterator($a);

        $b = [];
        while ($iterator->valid()) {
            $b[] = $iterator->current() + 1;
            $iterator->next();
        }
        return $b;

▼array_map()関数

        return array_map(fn ($e) => $e + 1, $a);

▼array_walk()関数

        $b = [];
        array_walk($a, fn ($e) => $b[] = $e + 1);
        return $b;

▼ベンチマークテストに使用したツール:

GitHub - macocci7/PHP-Benchmark: Simple Benchmark script for PHP.
Simple Benchmark script for PHP. Contribute to macocci7/PHP-Benchmark development by creating an account on GitHub.

▼テストコード

<?php

require_once __DIR__ . '/../vendor/autoload.php';

use Macocci7\PhpBenchmark\Benchmark;

// 配列生成
$n = 1000000;
$a = array_map(fn ($e) => rand(0, 100), range(1, $n)); // ワンライナーでスマートに見えるが高コストなおバカ処理
$iterator = new ArrayIterator($a);

Benchmark::codes([
    "foreach" => function () use ($a) {
        $b = [];
        foreach ($a as $v) {
            $b[] = $v + 1;
        }
        return $b;
    },
    "for" => function () use ($a) {
        $b = [];
        $count = count($a);
        for ($i = 0; $i < $count; $i++) {
            $b[] = $a[$i] + 1;
        }
        return $b;
    },
    "while i++" => function () use ($a) {
        $b = [];
        $i = 0;
        $count = count($a);
        while ($i < $count) {
            $b[] = $a[$i] + 1;
            $i++;
        }
        return $b;
    },
    "while current()/next()" => function () use ($a) {
        $b = [];
        while ($v = current($a)) {
            $b[] = $v + 1;
            next($a);
        }
        return $b;
    },
    "while Iterator" => function () use ($iterator) {
        $b = [];
        while ($iterator->valid()) {
            $b[] = $iterator->current() + 1;
            $iterator->next();
        }
        return $b;
    },
    "array_map" => function () use ($a) {
        return array_map(fn ($e) => $e + 1, $a);
    },
    "array_walk" => function () use ($a) {
        $b = [];
        array_walk($a, fn ($e) => $b[] = $e + 1);
        return $b;
    },
], 1);

▼実行環境:

  • PHP Version: 8.2.29 / 8.3.25 / 8.4.12
  • OS: Ubuntu24.04.2 LTS on WSL2 (Windows11 26100.6584)
  • Machine: AMD Ryzen 7 7730U(2.00GHz) / RAM 16GB / 64bit

▼実行結果

・8.2

1:                foreach => Time: 0.053075 sec  Avg: 0.0530750751 sec
2:                    for => Time: 0.070055 sec  Avg: 0.0700550079 sec
3:              while i++ => Time: 0.068458 sec  Avg: 0.0684578419 sec
4: while current()/next() => Time: 0.003387 sec  Avg: 0.0033869743 sec
5:         while Iterator => Time: 0.440034 sec  Avg: 0.4400339127 sec
6:              array_map => Time: 0.252750 sec  Avg: 0.2527501583 sec
7:             array_walk => Time: 0.349774 sec  Avg: 0.3497738838 sec

・8.3

1:                foreach => Time: 0.042910 sec  Avg: 0.0429100990 sec
2:                    for => Time: 0.074398 sec  Avg: 0.0743980408 sec
3:              while i++ => Time: 0.069607 sec  Avg: 0.0696070194 sec
4: while current()/next() => Time: 0.003456 sec  Avg: 0.0034558773 sec
5:         while Iterator => Time: 0.442884 sec  Avg: 0.4428839684 sec
6:              array_map => Time: 0.257925 sec  Avg: 0.2579250336 sec
7:             array_walk => Time: 0.339145 sec  Avg: 0.3391449451 sec

・8.4

1:                foreach => Time: 0.043218 sec  Avg: 0.0432181358 sec
2:                    for => Time: 0.070480 sec  Avg: 0.0704798698 sec
3:              while i++ => Time: 0.068050 sec  Avg: 0.0680499077 sec
4: while current()/next() => Time: 0.003429 sec  Avg: 0.0034289360 sec
5:         while Iterator => Time: 0.418805 sec  Avg: 0.4188048840 sec
6:              array_map => Time: 0.252669 sec  Avg: 0.2526688576 sec
7:             array_walk => Time: 0.341707 sec  Avg: 0.3417069912 sec

▼実行結果をグラフにしてみました

▼実行結果を表にまとめたもの

※「コメント」はChat-GPTによる評価(コードを見ずに勝手につけた正しくないコメント)

ループ方法PHP 8.2PHP 8.3PHP 8.4コメント
foreach0.0530750.0429100.043218安定して高速、内部最適化済み
for0.0700550.0743980.070480count()毎回呼び出しは遅め
while i++0.0684580.0696070.068050forとほぼ同等
while current()/next()0.0033870.0034560.003429最速、内部ポインタ操作による軽量走査
while Iterator0.4400340.4428840.418805遅い、オブジェクト生成のコスト大
array_map0.2527500.2579250.252669コールバック呼び出しのオーバーヘッド
array_walk0.3497740.3391450.341707コールバック+参照渡しでやや遅い

▼上の表を基に、バージョン毎に最速のものを基準値1.00とした場合の倍率

※Chat-GPTまとめ

ループ方法PHP 8.2PHP 8.3PHP 8.4
foreach15.6612.4212.58
for20.7021.5320.50
while i++20.2020.1419.85
while current()/next()1.001.001.00
while Iterator129.95128.20122.18
array_map74.5374.6473.66
array_walk103.2997.9899.64

▼筆者による分析

実行時のPCのタスク状況に多少左右されますが、順位の傾向は同じです。

配列ポインタを使用するwhile文が桁違いで最速!!なんとforeach文の12倍以上の速さ!

array_map()やarray_walk()等の関数系は桁違いに遅い。。

イテレータを使ったwhile文がダントツ遅い。。

やはり、配列ポインタを使うwhile文 current()/next() のループはC言語に近い動きで、PHP用の余計な処理が少ないせいで速いんですかね。

foreach文が思考停止でループするだけだから最速かと思っていましたが、そんなことはありませんでした。

for文とwhile文 $i++ はほぼ同じ。多分、$i++が入る分だけforeach文より遅いと思われ。

array_map()、array_walk()の関数系は圧倒的に高コスト(foreach文の約6倍、for文・while文の約3.5倍)。ループの各ステップで毎回コールバックの呼出が発生することが原因でしょう。

while文 イテレータ は 圧倒的に遅いarray_map()のさらに約1.7倍。。

ループのステップ毎に毎回 valid()/current()/next() の3度のメソッド呼出が祟っていることは間違いないでしょう。

▼まとめ

速い順に並べると:

1.while current()/next()

2.foreach

3.while i++

4.for

5.array_map()

6.array_walk()

7.while文 Iterator

配列ポインタを使うwhile文が最速なのは判ったけど、

見た目が美しくなくて嫌なので、foreachに走りがち。。

多分、普段はforeachで良いと思います。

パフォーマンス改善の必要に迫られたら、配列ポインタを使うwhile文にするということで。

要素数が数千程度ならarray_map()、array_walk()も全然アリだと思います。

要素数ひゃくまんで0.4秒未満ですから。

  • 1
  • 0
  • 0
  • 0

コメント

タイトルとURLをコピーしました