PHP массив объектов не линейно масштабируется, в то время как глобальные массивы делают?

Почему при использовании массива in-object в качестве свойства возникает серьезная проблема с производительностью, а не при использовании глобальной переменной массива php?

Чтобы протестировать эту проблему, я создал следующий эталонный тест, который хранит все больший массив с stdClass в качестве узла, два теста были запущены, один с использованием свойства массива в классе, другой - глобальный массив.

Тестовый код

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?

 Marc B23 мая 2012 г., 16:24
Вы все еще используете массив в объекте. пытатьсяglobal $history; $history[] = ... вместо.
 Nick23 мая 2012 г., 16:16
@MarcB Есть изменение, но оно не является значительным (изменение истории для сохранения только true$this->_event_history[] = [true] дает 9532 в секунду за 10 секунд. @NikiC У вас есть совет, как можно это урезать?
 Marc B23 мая 2012 г., 16:09
Сомневаюсь, что это проблема с массивом, но это может быть ООП, поскольку вы строите этот массив внутри объекта - много ООП. Если вы временно замените этот элемент объекта стандартной глобальной переменной, производительность изменится вообще?
 Nick23 мая 2012 г., 16:32
@MarcB Моя ошибка, что я неправильно прочитал комментарий, и кажется, что замена его глобальной переменной действительно дает результаты, почти идентичные тем, что выполняются без истории событий ... поэтому я думаю, что вопрос действительно заключается в том, что массив объектов не масштабируется линейно?
 NikiC23 мая 2012 г., 16:11
Не могли бы вы свести этот вопрос к актуальной проблеме, оставив весь материал prggmr? Это затрудняет понимание вопроса.

Ответы на вопрос(2)

this is more of an observation than an answer. Похоже, что SplObjectStorage довольно медленно. Кроме того, этот array_push намного быстрее, чем $ array [] = 'item' apos ;;

Отказ от ответственности: извинения за небрежный код :)

<?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.';
 30 мая 2012 г., 01:04
@AlixAxel: вероятно, потому что это не ответ. Я не отказываюсь от голосования, потому что кажется, что код полезен для изучения характеристик PHP.
 28 мая 2012 г., 23:27
Я хотел бы знать причину, почему это было понижено, кажется разумным ответом.
Решение Вопроса

[] вarray()и мне тоже пришлось исправить твою строку № 12: от$a=new MyTest($size), чтобы$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); после случая объекта (чтобы получить более согласованный журнал памяти), а также заменил ваши 5000000 на 1000000, потому что у меня только 2 ГБ ОЗУ. Вот что я получил:

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

Как вы можете видеть, добавление в массив внутри вызова функции стоит почти столько же (и имеет такое же нелинейное поведение, что и) внутри исходного вызова метода. Одно можно сказать наверняка:

It's the function calls that eat up CPU time!

Что касается нелинейного поведения, оно становится действительно очевидным только выше определенного порога. Хотя все три случая имеют одинаковое поведение памяти (из-за неполного сбора gargabe это проявляется только в случае «простого массива» и «массива внутри функции» в этом журнале), это «массив внутри метода» и «массив внутри функции»; случаи, которые имеют одинаковое поведение времени выполнения. Это означает, что сами вызовы функций вызывают нелинейное увеличение времени. Мне кажется, что это можно сказать:

The amount of data that is around during a function call influences its duration.

Чтобы убедиться в этом, я заменил все$a[] с$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. Обратите также внимание на то, что различия во времени исполнения в трех случаях менее выражены, чем раньше. Помните, что самая внутренняя операция одинакова во всех случаях.

Я не собираюсь пытаться полностью объяснить все это (сборник gargabe при вызове функции? Фрагментация памяти? ...?), Но я думаю, что я, тем не менее, собрал некоторую полезную информацию, для всех здесь и для себя тоже.

 Nick30 мая 2012 г., 04:01
Хороший ответ здесь ... это действительно требует погружения в источник php, так как мне кажется, что при доступе к переменным между их контекстом что-то происходит, что вызывает это огромное сокращение времени использования и учитывая, что они являются просто указателями и ссылками ( они не должны быть?) размер данных не должен иметь значения, так как ничего не записывается нигде, кроме дополнительных данных, добавленных ... или, возможно, это просто встреча с некоторыми решениями, которые разработчики имеют взятые, когда они разработали язык, который, возможно, остался незамеченным ...
 30 мая 2012 г., 03:17
Также, если вы не добавляетеunset($mytest); все будет примерно на 10% медленнее.

Ваш ответ на вопрос