Вопросы производительности

Предыдущий раздел уже упоминал, что простой сбор корней незначительно влияет на производительность, но это если сравнивать 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";
}
}

?>
Сравнение потребления памяти в PHP 5.2 и PHP 5.3

В этом академическом примере создаётся объект, в котором устанавливается свойство, которое указывает на сам объект. Когда в скрипте переменной $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, которые часто выполняются дольше, чем скрипты для веба.

Добавить

Примечания пользователей 2 notes

up
21
Talisman
8 years ago
The 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.
up
19
Dmitry dot Balabka at gmail dot com
6 years ago
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.php
To Top