Предыдущий раздел уже упоминал, что простой сбор корней незначительно влияет на производительность, но это если сравнивать 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, которые часто выполняются дольше, чем скрипты для веба.
There is a possibility to get GC performance stats without PHP recompilation. Starting from Xdebug version 2.6 you are able to enable stats collection into the file (default dir /tmp with name gcstats.%p):
php -dxdebug.gc_stats_enable=1 your_script.phpThe GC, unfortunately, as expounded in the examples above, has the tendency to promote lazy programming.
Clearly the benefits of the GC to assist in memory management are there, and help to maintain a stable system, but it is no excuse to not plan and test your code properly.
Always re-read your code critically and objectively to ensure that you are not introducing memory leaks unintentionally.