Предыдущий раздел уже упоминал, что простой сбор корней незначительно влияет на производительность, но это если сравнивать PHP 5.2 с PHP 5.3. Хотя запись корней в буфер по сравнению с отсутствием такой записи в PHP 5.2 замедляет работу приложения, другие изменения в работе PHP 5.3 во время выполнения кода предотвратили даже проявление этой конкретной потери производительности.
Производительность страдает в двух главных областях. Первая область — уменьшение размера памяти, которую PHP тратит на запись корней, а вторая — задержка во время выполнения кода, когда механизм сборки мусора очищает память. Рассмотрим обе проблемы.
Первая причина появления в языке механизма сборки мусора, состоит в уменьшении размера памяти, которую занимает мусор, путём очистки переменных с циклическими ссылками в тот момент, когда выполнятся предварительные условия. В реализации PHP сборка мусора начинается, как только заполняется корневой буфер или при вызове функции gc_collect_cycles(). График ниже показывает, как скрипт под графиком занимает память в PHP 5.2 и PHP 5.3, без учёта памяти, которую занимает сам PHP при запуске.
Пример #1 Пример использования памяти
<?php
class Foo
{
public $var = '3.14159265359';
public $self;
}
$baseMemory = memory_get_usage();
for ($i = 0; $i <= 100000; $i++) {
$a = new Foo();
$a->self = $a;
if ($i % 500 === 0) {
echo sprintf('%8d: ', $i), memory_get_usage() - $baseMemory, "\n";
}
}
?>
В этом академическом примере создаётся объект, в котором устанавливается свойство, которое указывает на сам объект. Когда в скрипте переменной $a на следующей итерации цикла повторно присваивается значение, происходит типичная утечка памяти. В примере утекает память для двух zval-контейнеров — контейнера объекта и контейнера свойства объекта, — но алгоритм находит только один корень: переменную, которую удалили. Как только после 10 000 итераций (если PHP-сборка разрешает только 10 000 корней) корневой буфер заполняется, срабатывает механизм сборки мусора и память, которую занимают эти корни, освобождается. Этот процесс хорошо виден на неравномерном графике потребления памяти PHP 5.3: после каждых 10 000 итераций график проседает. Сам механизм в примере совершает не много работы, потому что структура утечек проста. Из графика видно, что максимальное потребление памяти в PHP 5.3 составило около 9 МБ, тогда как в PHP 5.2 потребление памяти продолжает расти.
Вторая область, в которой механизм сборки мусора влияет на производительность, — потеря времени, которое требуется сборщику мусора для освобождения «утечки» памяти. Чтобы понять степень влияния, изменим предыдущий скрипт путём добавления количества итераций и удаления промежуточных показателей потребления памяти. После изменения скрипт выглядит вот так:
Пример #2 Влияние на производительность
<?php
class Foo
{
public $var = '3.14159265359';
public $self;
}
for ($i = 0; $i <= 1000000; $i++) {
$a = new Foo();
$a->self = $a;
}
echo memory_get_peak_usage(), "\n";
?>
Запустим скрипт два раза: с включённой опцией zend.enable_gc и без неё.
Пример #3 Запуск скрипта
time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php # и time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php
На тестовой машине первая команда выполняется примерно 10.7 секунды, а вторая примерно 11.4 секунды. Это примерно на 7 % медленнее. Однако максимальное потребление памяти скриптом уменьшилось на 98 % — с 931 до 10 МБ. Этот тест производительности не научный и даже не представляет реальное приложение, но показывает преимущества в работе с памятью, которые даёт механизм сборки мусора. Хорошо то, что замедление скрипта каждый раз составляет одни и те же 7 %, тогда как экономия памяти постоянно увеличивается по мере того, как алгоритм во время выполнения скрипта обнаруживает всё больше циклических ссылок.
PHP умеет выдавать больше информации о том, как механизм сборки
мусора выполняется в PHP. Но для этого потребуется перекомпилировать PHP,
чтобы включить код теста производительности и сбора данных. До запуска команды
./configure
с параметрами, которые требуются пользователю,
потребуется установить для переменной окружения CFLAGS
значение
-DGC_BENCH=1
. Следующая последовательность должна сработать:
Пример #4 Пример перекомпиляции PHP для включения теста производительности сборки мусора
export CFLAGS=-DGC_BENCH=1 ./config.nice make clean make
При повторном запуске приведенного примера кода с двоичным файлом PHP, который только что создали, после завершения выполнения PHP выведет следующее:
Пример #5 Статистика сборки мусора
GC Statistics ------------- Runs: 110 Collected: 2072204 Root buffer length: 0 Root buffer peak: 10000 Possible Remove from Marked Root Buffered buffer grey -------- -------- ----------- ------ ZVAL 7175487 1491291 1241690 3611871 ZOBJ 28506264 1527980 677581 1025731
Самую информативную статистику показывает первый блок. Видно, что механизм сборки мусора запускался 110 раз, и суммарно освободил больше 2 миллионов записей в памяти. Как только механизм сборки мусора сработал хотя бы один раз, показатель пика корневого буфера (Root buffer peak) будет равняться 10 000.
Сборщик мусора в PHP вызывает замедление работы только во время работы алгоритма сборки циклических ссылок, тогда как в стандартных скриптах меньшего размера производительность не падает.
Когда механизм сборки циклов все-таки запускается для стандартных скриптов, объём памяти, которую экономит механизм, разрешает одновременно запускать на сервере большее количество скриптов, поскольку в целом скрипты занимают не так много памяти.
Преимущества заметнее для скриптов, которые работают долго — большие наборы тестов или демоны. Новый механизм существенно сокращает утечки памяти для приложений, которые работают с расширением » PHP-GTK, которые часто выполняются дольше, чем скрипты для веба.