9

オブジェクト内配列をプロパティとして使用する場合とグローバルphp配列変数を使用する場合には、パフォーマンスに大きな問題があります。なぜですか。

この問題をベンチマークするために、stdClassをノードとして使用して、ますます大きくなる配列を格納する次のベンチマークを作成しました。1つはクラスの配列プロパティを使用し、もう1つはグローバル配列を使用して2つのテストを実行しました。

テストコード

ini_set('memory_limit', '2250M');
class MyTest {
    public $storage = [];
    public function push(){
        $this->storage[] = [new stdClass()];
    }
}

echo "Testing Objects".PHP_EOL;
for($size = 1000; $size < 5000000; $size *= 2) {
    $start = milliseconds();
    for ($a=new MyTest(), $i=0;$i<$size;$i++) {
        $a->push();
    }
    $end = milliseconds();
    echo "Array Size $size".PHP_EOL;
    echo $end - $start . " milliseconds to perform".PHP_EOL;
}
echo "================".PHP_EOL;
echo "Testing Array".PHP_EOL;
for($size = 1000; $size < 5000000; $size *= 2) {
    $start = milliseconds();
    for ($a=[], $i=0;$i<$size;$i++) {
        $a[] = [new stdClass()];
    }
    $end = milliseconds();
    echo "Array Size $size".PHP_EOL;
    echo $end - $start . " milliseconds to perform".PHP_EOL;
}

そして衝撃的な結果:

Testing Objects
Array Size 1000
2 milliseconds to perform
Array Size 2000
3 milliseconds to perform
Array Size 4000
6 milliseconds to perform
Array Size 8000
12 milliseconds to perform
Array Size 16000
35 milliseconds to perform
Array Size 32000
97 milliseconds to perform
Array Size 64000
246 milliseconds to perform
Array Size 128000
677 milliseconds to perform
Array Size 256000
2271 milliseconds to perform
Array Size 512000
9244 milliseconds to perform
Array Size 1024000
31186 milliseconds to perform
Array Size 2048000
116123 milliseconds to perform
Array Size 4096000
495588 milliseconds to perform
================
Testing Array
Array Size 1000
1 milliseconds to perform
Array Size 2000
2 milliseconds to perform
Array Size 4000
4 milliseconds to perform
Array Size 8000
8 milliseconds to perform
Array Size 16000
28 milliseconds to perform
Array Size 32000
61 milliseconds to perform
Array Size 64000
114 milliseconds to perform
Array Size 128000
245 milliseconds to perform
Array Size 256000
494 milliseconds to perform
Array Size 512000
970 milliseconds to perform
Array Size 1024000
2003 milliseconds to perform
Array Size 2048000
4241 milliseconds to perform
Array Size 4096000
14260 milliseconds to perform

オブジェクト呼び出し自体の明らかなオーバーヘッドに加えて、オブジェクト配列プロパティは、配列が大きくなると3〜4倍長くかかることがありますが、標準のグローバル配列変数の場合はそうではありません。

この問題に関する考えや回答はありますか?これはPHPエンジンで発生する可能性のあるバグですか?

4

2 に答える 2

6

PHP 5.3.9 でコードをテストしました。そのためには、 に変換[]する必要array()があり、また、行 #12: from $a=new MyTest($size), to $mytest=new MyTest($size)(ところで、コンストラクターの引数は黙って無視されます、おかしい) も修正する必要がありました。このコードも追加しました:

echo "================".PHP_EOL;
echo "Testing Function".PHP_EOL;
for($size = 1000; $size < 1000000; $size *= 2) {
    $start = milliseconds();
    for ($a=array(), $i=0;$i<$size;$i++) {
        my_push($a);
    }
    $end = milliseconds();
    echo "Array Size $size".PHP_EOL;
    echo $end - $start . " milliseconds to perform".PHP_EOL;
    echo "memory usage: ".memory_get_usage()." , real: ".memory_get_usage(true).PHP_EOL;
}

function my_push(&$a)
{
   $a[] = array(new stdClass());
}

同じ時点でメモリ使用量の行をループに追加しunset($mytest);、オブジェクト ケースの後に追加し (より一貫性のあるメモリ ログを取得するため)、2 GB の RAM しかないため、5000000 を 1000000 に置き換えました。これは私が得たものです:

Testing Objects
Array Size 1000
2 milliseconds to perform
memory usage: 1666376 , real: 1835008
Array Size 2000
5 milliseconds to perform
memory usage: 2063280 , real: 2097152
Array Size 4000
10 milliseconds to perform
memory usage: 2857008 , real: 2883584
Array Size 8000
19 milliseconds to perform
memory usage: 4444456 , real: 4718592
Array Size 16000
44 milliseconds to perform
memory usage: 7619392 , real: 8126464
Array Size 32000
103 milliseconds to perform
memory usage: 13969256 , real: 14417920
Array Size 64000
239 milliseconds to perform
memory usage: 26668936 , real: 27262976
Array Size 128000
588 milliseconds to perform
memory usage: 52068368 , real: 52690944
Array Size 256000
1714 milliseconds to perform
memory usage: 102867104 , real: 103546880
Array Size 512000
5452 milliseconds to perform
memory usage: 204464624 , real: 205258752
================
Testing Array
Array Size 1000
1 milliseconds to perform
memory usage: 18410640 , real: 20709376
Array Size 2000
4 milliseconds to perform
memory usage: 18774760 , real: 20709376
Array Size 4000
7 milliseconds to perform
memory usage: 19502976 , real: 20709376
Array Size 8000
13 milliseconds to perform
memory usage: 20959360 , real: 21233664
Array Size 16000
29 milliseconds to perform
memory usage: 23872176 , real: 24379392
Array Size 32000
61 milliseconds to perform
memory usage: 29697720 , real: 30146560
Array Size 64000
124 milliseconds to perform
memory usage: 41348856 , real: 41943040
Array Size 128000
280 milliseconds to perform
memory usage: 64651088 , real: 65273856
Array Size 256000
534 milliseconds to perform
memory usage: 111255536 , real: 111935488
Array Size 512000
1085 milliseconds to perform
memory usage: 204464464 , real: 205258752
================
Testing Function
Array Size 1000
357 milliseconds to perform
memory usage: 18410696 , real: 22544384
Array Size 2000
4 milliseconds to perform
memory usage: 18774768 , real: 22544384
Array Size 4000
9 milliseconds to perform
memory usage: 19503008 , real: 22544384
Array Size 8000
17 milliseconds to perform
memory usage: 20959392 , real: 22544384
Array Size 16000
36 milliseconds to perform
memory usage: 23872208 , real: 24379392
Array Size 32000
89 milliseconds to perform
memory usage: 29697720 , real: 30146560
Array Size 64000
224 milliseconds to perform
memory usage: 41348888 , real: 41943040
Array Size 128000
529 milliseconds to perform
memory usage: 64651088 , real: 65273856
Array Size 256000
1587 milliseconds to perform
memory usage: 111255616 , real: 111935488
Array Size 512000
5244 milliseconds to perform
memory usage: 204464512 , real: 205258752

ご覧のとおり、関数呼び出し内での配列への追加は、元のメソッド呼び出し内での追加とほぼ同じコストがかかります (そして同じ非線形動作になります)。1つ確かなことは次のとおりです。

CPU 時間を消費するのは関数呼び出しです!

非線形の動作に関しては、特定のしきい値を超えた場合にのみ明らかになります。3 つのケースはすべて同じメモリ動作を持ちますが (ガガベ コレクションが不完全であるため、このログでは「プレーン配列」と「関数内の配列」のケースでのみ明らかです)、それは「メソッド内の配列」と「関数内の配列」の場合は、実行時の動作が同じです。これは、時間の非線形増加を引き起こすのは関数呼び出し自体であることを意味します。これは次のように言えるように私には思えます。

関数呼び出し中に存在するデータの量は、その期間に影響します。

これを確認するために、すべて$a[]を1000000 に置き換え$a[0]、すべての 1000000 を 5000000 に置き換えて (同様の合計実行時間を取得するため)、次の出力を得ました。

Testing Objects
Array Size 1000
2 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 2000
4 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 4000
8 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 8000
15 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 16000
31 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 32000
62 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 64000
123 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 128000
246 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 256000
493 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 512000
985 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 1024000
1978 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 2048000
3965 milliseconds to perform
memory usage: 1302672 , real: 1572864
Array Size 4096000
7905 milliseconds to perform
memory usage: 1302672 , real: 1572864
================
Testing Array
Array Size 1000
1 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 2000
3 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 4000
5 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 8000
10 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 16000
20 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 32000
40 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 64000
80 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 128000
161 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 256000
322 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 512000
646 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 1024000
1285 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 2048000
2574 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 4096000
5142 milliseconds to perform
memory usage: 1302464 , real: 1572864
================
Testing Function
Array Size 1000
1 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 2000
4 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 4000
6 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 8000
14 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 16000
26 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 32000
53 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 64000
105 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 128000
212 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 256000
422 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 512000
844 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 1024000
1688 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 2048000
3377 milliseconds to perform
memory usage: 1302464 , real: 1572864
Array Size 4096000
6814 milliseconds to perform
memory usage: 1302464 , real: 1572864

現在、時間がほぼ完全に直線的になっていることに注意してください。もちろん、配列サイズは現在 1 のままです。また、3 つのケースの実行時間の差が以前ほど顕著ではないことにも注意してください。最も内側の操作はすべての場合で同じであることに注意してください。

このすべてを完全に説明しようとするつもりはありませんが (関数呼び出しでのガベージ コレクション? メモリの断片化? ...?)、それでも、ここにいるすべての人にとっても、私自身にとっても、いくつかの有用な情報を収集できたと思います。

于 2012-05-29T20:31:50.147 に答える
2

これをすべてコメントに投稿することはできないので、これは答えというよりは観察です。SplObjectStorageはかなり遅いようです。また、そのarray_pushは$ array []='item'よりもはるかに高速です。

免責事項:ずさんなコードの謝罪:)

<?php

$time = microtime();
$time = explode(' ', $time);
$time = $time[1] + $time[0];
$start = $time;

$iteration = 10000;

switch ($_REQUEST['test'])
{
    case 1:
        $s = new SplObjectStorage();

        for ($i = 0; $i < $iteration; $i++) {
            $obj = new stdClass;
            $s[$obj] = 'test';
        }
        break;
    case 2:

        $s = array();
        for ($i = 0; $i < $iteration; $i++) {
            $obj = new stdClass;
            $s[$i] = $obj;
        }
        break;

    case 3:
        class Test {
            public $data = array();
        }
        $s = new Test;
        for ($i = 0; $i < $iteration; $i++) {
            $obj = new stdClass;
            $s->data[] = $obj;
        }
        break;

    case 4:
        class Test {
            public static $data = array();
        }
        $s = new Test;
        for ($i = 0; $i < $iteration; $i++) {
            $obj = new stdClass;
            $s->data[] = $obj;
        }
        break;  
    case 5:
        class Test {
            public $data = array();
        }
        $s = new Test;
        for ($i = 0; $i < $iteration; $i++) {
            $obj = new stdClass;
            array_push($s->data, $obj);
        }
        break;  
    default:
        echo 'Type in ?test=#';
}

$time = microtime();
$time = explode(' ', $time);
$time = $time[1] + $time[0];
$finish = $time;
$total_time = round(($finish - $start), 6);
echo 'Page generated in '.$total_time.' seconds.';
于 2012-05-28T18:31:27.113 に答える