mirror of
https://github.com/php/doc-ru.git
synced 2026-03-24 07:42:22 +01:00
688 lines
39 KiB
XML
688 lines
39 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
||
<!-- EN-Revision: 23e84882d6654f995166d17e5610af6bf00ef18c Maintainer: irker Status: ready -->
|
||
<!-- Reviewed: no -->
|
||
<chapter xml:id="features.gc" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||
<title>Сборка мусора</title>
|
||
|
||
<para>
|
||
Раздел описывает достоинства нового механизма сборки мусора
|
||
(Garbage Collection, или GC), который появился в PHP 5.3.
|
||
</para>
|
||
|
||
<sect1 xml:id="features.gc.refcounting-basics">
|
||
<title>Основы подсчёта ссылок</title>
|
||
<para>
|
||
Переменная PHP хранится в контейнере, который называется zval (англ. сокр.: Zend Value — Zend-значение).
|
||
Контейнер zval, кроме типа и значения переменной, также содержит два дополнительных
|
||
бита информации. Первый называется is_ref (англ. сокр.: Is Reference — ссылка ли?)
|
||
и представляет логическое значение, которое указывает, включил ли PHP переменную в «набор ссылок» или нет.
|
||
За счёт бита is_ref PHP-движок знает, как отличать обычные переменные от ссылок. Поскольку PHP разрешает
|
||
пользовательские ссылки, которые создают оператором &, контейнер zval
|
||
также содержит внутренний механизм подсчёта ссылок для оптимизации работы памяти.
|
||
Вторая часть дополнительной информации называется refcount (англ. сокр.:
|
||
Reference Counter — счётчик ссылок) и содержит количество имён переменных, или другое название — символов,
|
||
которые указывают на этот zval-контейнер. Каждый символ хранится в таблице символов. У каждой
|
||
области видимости переменных своя таблица символов. PHP создаёт отдельную область видимости для главного скрипта,
|
||
который срабатывает при запросе из браузера, и отдельную область видимости для каждой функции или метода.
|
||
</para>
|
||
<para>
|
||
Контейнер zval создаётся при объявлении новой переменной, которой
|
||
присваивается константное значение, например:
|
||
<example>
|
||
<title>Создание нового контейнера zval</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$a = "new string";
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
В примере в текущей области видимости создаётся новый символ с именем "<literal>a</literal>"
|
||
и новый контейнер переменной с типом <type>string</type> и значением <literal>new string</literal>.
|
||
Бит is_ref по умолчанию задаётся равным &false;, поскольку не создали ни одной пользовательской
|
||
ссылки. Значение же бита refcount задаётся равным <literal>1</literal>, поскольку с этим контейнером
|
||
PHP связал только один символ. Обратите внимание, zval-контейнеры со значением &true; в бите is_ref
|
||
и значением <literal>1</literal> в бите refcount движок преобразовывает в контейнеры стандартных переменных
|
||
путём установки для бита is_ref значения &false;. Модуль <link xlink:href="&url.xdebug;">Xdebug</link>
|
||
умеет выводить эту информацию через функцию <function>xdebug_debug_zval</function>.
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Вывод информации о zval-контейнере</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$a = "new string";
|
||
xdebug_debug_zval('a');
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=1, is_ref=0)='new string'
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
Присваивание одной переменной другому имени переменной увеличивает счётчик ссылок.
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Увеличение счётчика ссылок контейнера zval</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$a = "new string";
|
||
$b = $a;
|
||
xdebug_debug_zval('a');
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=2, is_ref=0)='new string'
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
Значение счётчика ссылок здесь равно <literal>2</literal>, поскольку с одним
|
||
и тем же контейнером переменной PHP связал как символ
|
||
<varname>a</varname>, так и символ <varname>b</varname>. PHP достаточно умён, чтобы
|
||
не копировать сам контейнер, пока этого не требуется. Как только
|
||
счётчик ссылок refcount становится равным нулю, контейнеры переменных уничтожаются.
|
||
Счётчик ссылок refcount уменьшается на единицу, когда символ, который PHP связал с контейнером переменной,
|
||
выходит из области видимости (например, в конце функции),
|
||
или при удалении символа (например, при вызове языковой конструкции <function>unset</function>).
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Уменьшение счётчика ссылок контейнера zval</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$a = "new string";
|
||
$c = $b = $a;
|
||
xdebug_debug_zval('a');
|
||
|
||
$b = 42;
|
||
xdebug_debug_zval('a');
|
||
|
||
unset( $c );
|
||
xdebug_debug_zval('a');
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=3, is_ref=0)='new string'
|
||
a: (refcount=2, is_ref=0)='new string'
|
||
a: (refcount=1, is_ref=0)='new string'
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
Вызов <literal>unset($a);</literal> в этом месте
|
||
удалит контейнер переменной из памяти, включая тип и значение.
|
||
</para>
|
||
|
||
<sect2 xml:id="features.gc.compound-types">
|
||
<title>Составные типы данных</title>
|
||
|
||
<para>
|
||
Хранение составных типов данных наподобие массивов (<type>array</type>)
|
||
и объектов (<type>object</type>) усложняется. Каждый элемент массива или каждое свойство объекта
|
||
хранится в отдельной таблице символов, в противоположность хранению
|
||
скалярных (<type>scalar</type>) значений одной и той же области видимости в одной таблице символов.
|
||
Следующий пример создаст сразу три zval-контейнера:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Создание zval-контейнера для массива (<type>array</type>)</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$a = array('meaning' => 'life', 'number' => 42);
|
||
xdebug_debug_zval('a');
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=1, is_ref=0)=array (
|
||
'meaning' => (refcount=1, is_ref=0)='life',
|
||
'number' => (refcount=1, is_ref=0)=42
|
||
)
|
||
]]>
|
||
</screen>
|
||
<para>Графически:</para>
|
||
<mediaobject>
|
||
<alt>Контейнеры для простого массива</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/simple-array.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
PHP создаёт три zval-контейнера для символов: <varname>a</varname>, <varname>meaning</varname>
|
||
и <varname>number</varname>. Аналогичные правила применяются для увеличения и уменьшения
|
||
количества ссылок. В следующем примере в массив добавляется ещё один элемент,
|
||
которому устанавливается значение другого элемента массива:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Добавление уже существующего элемента в массив</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$a = array('meaning' => 'life', 'number' => 42);
|
||
$a['life'] = $a['meaning'];
|
||
xdebug_debug_zval('a');
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=1, is_ref=0)=array (
|
||
'meaning' => (refcount=2, is_ref=0)='life',
|
||
'number' => (refcount=1, is_ref=0)=42,
|
||
'life' => (refcount=2, is_ref=0)='life'
|
||
)
|
||
]]>
|
||
</screen>
|
||
<para>Графически:</para>
|
||
<mediaobject>
|
||
<alt>Контейнеры для простого массива со ссылками</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/simple-array2.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
Из вывода модуля Xdebug видно, что как старый, так и новый элемент массива
|
||
теперь указывает на zval-контейнер, значение refcount которого равно <literal>2</literal>.
|
||
Хотя вывод модуля Xdebug показывает два zval-контейнера со значением <literal>'life'</literal>,
|
||
контейнеры одинаковы. Функция <function>xdebug_debug_zval</function> не показывает, что контейнеры одинаковы,
|
||
но добавление в вывод указателей памяти покажет.
|
||
</para>
|
||
<para>
|
||
Элемент удаляется из массива аналогично удалению символа
|
||
из области видимости: счётчик ссылок refcount того контейнера, на который указывает элемент массива, уменьшается.
|
||
Контейнер переменной удаляется из памяти, когда значение в бите refcount достигает нуля.
|
||
Пример:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Удаление элемента из массива</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$a = array('meaning' => 'life', 'number' => 42);
|
||
$a['life'] = $a['meaning'];
|
||
unset($a['meaning'], $a['number']);
|
||
xdebug_debug_zval('a');
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=1, is_ref=0)=array (
|
||
'life' => (refcount=1, is_ref=0)='life'
|
||
)
|
||
]]>
|
||
</screen>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
Ситуация станет интереснее, если добавить массив сам в себя.
|
||
В следующем примере вводится оператор присваивания по ссылке, чтобы
|
||
PHP не создал копию массива.
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Пример добавления массива как элемента самого себя</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?php
|
||
|
||
$a = array('one');
|
||
$a[] =& $a;
|
||
xdebug_debug_zval('a');
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
&example.outputs.similar;
|
||
<screen>
|
||
<![CDATA[
|
||
a: (refcount=2, is_ref=1)=array (
|
||
0 => (refcount=1, is_ref=0)='one',
|
||
1 => (refcount=2, is_ref=1)=...
|
||
)
|
||
]]>
|
||
</screen>
|
||
<para>Графически:</para>
|
||
<mediaobject>
|
||
<alt>Контейнеры массива с циклическими ссылками</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/loop-array.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
Видно, что переменная массива <varname>a</varname>, и второй элемент
|
||
с индексом <varname>1</varname> теперь указывают на контейнер переменной, значение refcount которого равно
|
||
<literal>2</literal>. Символы «...» в выводе показывают рекурсию, что в этом примере
|
||
означает, что символы «...» указывают на исходный массив.
|
||
</para>
|
||
<para>
|
||
Как и раньше, при удалении переменной символ удаляется, а счётчик ссылок контейнера переменной,
|
||
на который указывает переменная, уменьшается на единицу.
|
||
Поэтому, если применить конструкцию unset к переменной <varname>$a</varname> после запуска приведённого кода,
|
||
счётчик ссылок контейнера переменной, на который указывают переменная <varname>$a</varname> и элемент массива
|
||
с индексом 1, уменьшится на единицу, с 2 до 1:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Удаление переменной массива <varname>$a</varname></title>
|
||
<screen>
|
||
<![CDATA[
|
||
(refcount=1, is_ref=1)=array (
|
||
0 => (refcount=1, is_ref=0)='one',
|
||
1 => (refcount=1, is_ref=1)=...
|
||
)
|
||
]]>
|
||
</screen>
|
||
<para>Графически:</para>
|
||
<mediaobject>
|
||
<alt>Контейнеры после удаления массива с циклическими ссылками, которые демонстрируют утечку памяти</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/leak-array.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</example>
|
||
</para>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="features.gc.cleanup-problems">
|
||
<title>Проблемы очистки</title>
|
||
<para>
|
||
Хотя ни в одной области видимости больше нет символа, который указывает
|
||
на структуру массива, структуру нельзя очистить, поскольку элемент массива с ключом 1 по-прежнему
|
||
указывает на этот же массив. Поскольку на структуру не указывает внешний символ,
|
||
пользователю недоступна очистка этой структуры; поэтому пользователь получает утечку памяти.
|
||
К счастью, PHP очистит эту структуру данных в конце запроса, но до того момента
|
||
данные будут занимать ценное место в памяти. Такая ситуация часто возникает
|
||
при реализации алгоритмов синтаксического анализа или других вещей, в которых дочерние элементы
|
||
указывают на родительские. С ещё большей вероятностью такая же ситуация возникает с объектами,
|
||
поскольку с объектами язык неявно работает «<link linkend="language.oop5.references">по ссылке</link>».
|
||
</para>
|
||
<para>
|
||
Не проблема, если такое случается раз или два,
|
||
но при тысяче или даже миллионе таких случаев утечки памяти уже станут проблемой.
|
||
Особенно в скриптах, которые работают долго, например, в демонах, в которых запрос
|
||
не заканчивается, или в крупных наборах модульных тестов.
|
||
Последний случай вызвал проблемы при запуске модульных тестов
|
||
для компонента Template библиотеки ez Components. В ряде случаев
|
||
требовалось больше 2 ГБ памяти, доступа к которой
|
||
на тестовом сервере не было.
|
||
</para>
|
||
</sect2>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="features.gc.collecting-cycles">
|
||
<title>Сбор циклических ссылок</title>
|
||
<para>
|
||
Традиционно механизмы подсчёта ссылок в памяти вроде тех, которые работали в PHP раньше,
|
||
не справлялись с утечками памяти, которые вызывали циклические ссылки;
|
||
однако с PHP 5.3.0 разработчики языка реализовали синхронный алгоритм из исследования
|
||
<link xlink:href="&url.gc-paper;">Concurrent Cycle Collection in Reference Counted Systems</link>
|
||
(англ. «Параллельный сбор циклических ссылок в системах подсчёта ссылок»), который решает эту проблему.
|
||
</para>
|
||
<para>
|
||
Полное описание работы алгоритма выходит за рамки раздела, но основы раздел объясняет.
|
||
Вначале установим базовые правила. Первое, PHP продолжает хранить в памяти и не считает
|
||
мусором контейнеры, значение бита refcount которых увеличилось.
|
||
Zval-контейнер, количество ссылок в котором уменьшилось до нуля,
|
||
освобождается из памяти. Поэтому циклические ссылки становятся мусорными, только когда
|
||
переменных со ссылкой на контейнер не осталось, а значение аргумента refcount в контейнере не обнулилось.
|
||
Второе, PHP умеет обнаруживать мусорные части в мусорных циклах путём
|
||
уменьшения количества ссылок в контейнерах на единицу и проверки, в каких
|
||
zval-контейнерах количество ссылок стало нулевым.
|
||
</para>
|
||
<para>
|
||
<mediaobject>
|
||
<alt>Алгоритм сборки мусора</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/gc-algorithm.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</para>
|
||
<para>
|
||
Алгоритм помещает возможные корни — zval-контейнеры — в «корневой буфер» и помечает
|
||
корни «фиолетовыми», чтобы не вызывать проверку мусорных циклов при каждом уменьшении
|
||
счётчика ссылок. Алгоритм следит и за тем, чтобы каждый возможный мусорный корень
|
||
попадал в буфер только один раз. Механизм сборки мусора для каждого zval-контейнера внутри буфера
|
||
стартует, только когда корневой буфер заполняется. Графически поведение показывает шаг A на рисунке выше.
|
||
</para>
|
||
<para>
|
||
На шаге B алгоритм выполняет поиск в глубину по каждому возможному корню,
|
||
чтобы однократно уменьшить количество ссылок в каждом контейнере на единицу,
|
||
и помечает корни «серыми». На шаге C алгоритм снова выполняет поиск в глубину
|
||
от каждого корневого узла, чтобы ещё раз проверить количество ссылок для каждого zval-контейнера.
|
||
Алгоритм помечает корни с нулевым количеством ссылок «белыми» (на рисунке — синим).
|
||
А если количество ссылок в контейнере больше нуля, начиная с этого корня поиск идёт в глубину
|
||
с обратным увеличением количества ссылок на единицу и пометкой корней «черными».
|
||
На последнем шаге, D, алгоритм обходит корневой буфер
|
||
и удаляет из него корни контейнеров. Алгоритм заодно проверяет, какие zval-контейнеры
|
||
на предыдущем шаге он пометил «белыми». Каждый «белый» zval-контейнер освобождается из памяти.
|
||
</para>
|
||
<para>
|
||
Теперь, когда есть базовое представление о работе алгоритма, вернёмся к тому,
|
||
как алгоритм интегрируется с PHP. По умолчанию сборщик мусора PHP включён.
|
||
Параметр <link linkend="ini.zend.enable-gc">zend.enable_gc</link> в файле &php.ini;
|
||
разрешает отключить сборку мусора.
|
||
</para>
|
||
<para>
|
||
При включённом сборщике мусора, алгоритм поиска циклических ссылок выполняется после каждого
|
||
наполнения корневого буфера. Фиксированный размер корневого буфера равняется 10 000 возможных корней,
|
||
хотя значение изменяется путём изменения значения константы <constant>GC_THRESHOLD_DEFAULT</constant>
|
||
в файле <literal>Zend/zend_gc.c</literal> исходного кода PHP и пересборки PHP.
|
||
При выключенном сборщике мусора алгоритм поиска циклических ссылок не запускается. Однако
|
||
в корневой буфер всё равно записываются возможные корни, независимо от активации механизма
|
||
сборки мусора через параметр конфигурации.
|
||
</para>
|
||
<para>
|
||
PHP прекратит запись возможных корней,
|
||
если корневой буфер заполнится при выключенном механизме сборки мусора.
|
||
Алгоритм не будет анализировать корни, которые не записал в буфер.
|
||
Поэтому если корни окажутся мусором с циклическими ссылками, они вызовут утечку памяти,
|
||
поскольку PHP не очистит их.
|
||
</para>
|
||
<para>
|
||
Причина постоянной записи корней в буфер даже при выключенном механизме сборки мусора
|
||
состоит в том, что записать корни быстрее, чем каждый раз, когда удаётся найти корень,
|
||
проверять, включили ли механизм сборки мусора. Однако сам механизм сборки мусора
|
||
и алгоритм анализа иногда занимают много времени.
|
||
</para>
|
||
<para>
|
||
Кроме изменения параметра конфигурации <link linkend="ini.zend.enable-gc">zend.enable_gc</link>,
|
||
доступен запуск механизма сборки мусора через вызов функции <function>gc_enable</function>
|
||
и остановка механизма функцией <function>gc_disable</function>.
|
||
Вызов этих функций даёт тот же эффект, что и включение или выключение механизма
|
||
в настройках конфигурации.
|
||
Возможна также принудительная сборка мусорных циклов, даже если корневой буфер ещё не заполнился.
|
||
Для этого вызывают функцию <function>gc_collect_cycles</function>, которая
|
||
возвращает количество циклических ссылок, которые собрал алгоритм.
|
||
</para>
|
||
<para>
|
||
Смысл включения и выключения механизма сборки мусора, а также ручного запуска механизма
|
||
состоит в чувствительности отдельных частей приложения
|
||
ко времени, когда автоматический запуск механизма сборки мусора не нужен.
|
||
Отключение сборщика мусора в конкретных
|
||
частях приложения создаёт риск утечки памяти, поскольку
|
||
отдельные корни не поместятся в ограниченный
|
||
корневой буфер. Поэтому лучше перед вызовом функции <function>gc_disable</function>
|
||
вызвать функцию <function>gc_collect_cycles</function>,
|
||
чтобы освободить память, риск потери которой возникает из-за возможных корней,
|
||
которые алгоритм уже записал в корневой буфер.
|
||
Это очистит буфер и даст больше места
|
||
для хранения корней, пока механизм сбора мусорных циклов будет выключен.
|
||
</para>
|
||
</sect1>
|
||
|
||
<sect1 xml:id="features.gc.performance-considerations">
|
||
<title>Вопросы производительности</title>
|
||
<para>
|
||
Предыдущий раздел уже упоминал, что простой сбор корней
|
||
незначительно влияет на производительность, но это если сравнивать PHP 5.2 с PHP 5.3.
|
||
Хотя запись корней в буфер по сравнению с отсутствием такой записи
|
||
в PHP 5.2 замедляет работу приложения, другие изменения в работе
|
||
PHP 5.3 во время выполнения кода предотвратили даже проявление этой конкретной потери производительности.
|
||
</para>
|
||
<para>
|
||
Производительность страдает в двух главных областях. Первая область —
|
||
уменьшение размера памяти, которую PHP тратит на запись корней, а вторая —
|
||
задержка во время выполнения кода, когда механизм сборки мусора очищает память.
|
||
Рассмотрим обе проблемы.
|
||
</para>
|
||
|
||
<sect2 xml:id="features.gc.performance-considerations.reduced-mem">
|
||
<title>Уменьшение размера памяти</title>
|
||
<para>
|
||
Первая причина появления в языке механизма сборки
|
||
мусора, состоит в уменьшении размера памяти, которую занимает мусор,
|
||
путём очистки переменных с циклическими ссылками в тот момент, когда выполнятся
|
||
предварительные условия. В реализации PHP сборка мусора начинается, как
|
||
только заполняется корневой буфер или при вызове функции
|
||
<function>gc_collect_cycles</function>.
|
||
График ниже показывает, как скрипт под графиком занимает память
|
||
в PHP 5.2 и PHP 5.3, без учёта памяти, которую занимает сам PHP при запуске.
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Пример использования памяти</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?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";
|
||
}
|
||
}
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
<mediaobject>
|
||
<alt>Сравнение потребления памяти в PHP 5.2 и PHP 5.3</alt>
|
||
<imageobject>
|
||
<imagedata fileref="en/features/figures/gc-benchmark.png" format="PNG"/>
|
||
</imageobject>
|
||
</mediaobject>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
В этом академическом примере создаётся объект, в котором устанавливается свойство,
|
||
которое указывает на сам объект. Когда в скрипте переменной <varname>$a</varname>
|
||
на следующей итерации цикла повторно присваивается значение,
|
||
происходит типичная утечка памяти.
|
||
В примере утекает память для двух zval-контейнеров — контейнера объекта
|
||
и контейнера свойства объекта, — но алгоритм находит только один корень: переменную, которую удалили.
|
||
Как только после 10 000 итераций (если PHP-сборка разрешает только
|
||
10 000 корней) корневой буфер заполняется, срабатывает механизм сборки мусора
|
||
и память, которую занимают эти корни, освобождается.
|
||
Этот процесс хорошо виден на неравномерном графике потребления памяти PHP 5.3:
|
||
после каждых 10 000 итераций график проседает.
|
||
Сам механизм в примере совершает не много работы, потому что
|
||
структура утечек проста. Из графика видно, что максимальное потребление памяти
|
||
в PHP 5.3 составило около 9 МБ, тогда как в PHP 5.2 потребление памяти продолжает расти.
|
||
</para>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="features.gc.performance-considerations.slowdowns">
|
||
<title>Замедление работы</title>
|
||
<para>
|
||
Вторая область, в которой механизм сборки мусора влияет на производительность, —
|
||
потеря времени, которое требуется сборщику мусора для освобождения «утечки» памяти.
|
||
Чтобы понять степень влияния, изменим предыдущий скрипт путём
|
||
добавления количества итераций и удаления промежуточных показателей потребления памяти.
|
||
После изменения скрипт выглядит вот так:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Влияние на производительность</title>
|
||
<programlisting role="php">
|
||
<![CDATA[
|
||
<?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";
|
||
|
||
?>
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
Запустим скрипт два раза: с включённой опцией
|
||
<link linkend="ini.zend.enable-gc">zend.enable_gc</link> и без неё.
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Запуск скрипта</title>
|
||
<programlisting role="shell">
|
||
<![CDATA[
|
||
time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php
|
||
# и
|
||
time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
На тестовой машине первая команда выполняется примерно 10.7 секунды,
|
||
а вторая примерно 11.4 секунды. Это примерно на 7 % медленнее. Однако
|
||
максимальное потребление памяти скриптом уменьшилось на 98 % — с 931 до 10 МБ.
|
||
Этот тест производительности не научный и даже не представляет реальное приложение,
|
||
но показывает преимущества в работе с памятью, которые даёт механизм сборки мусора.
|
||
Хорошо то, что замедление скрипта каждый раз составляет одни и те же 7 %,
|
||
тогда как экономия памяти постоянно увеличивается по мере того, как алгоритм
|
||
во время выполнения скрипта обнаруживает всё больше циклических ссылок.
|
||
</para>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="features.gc.performance-considerations.internal-stats">
|
||
<title>Внутренняя статистика сборщика мусора</title>
|
||
<para>
|
||
PHP умеет выдавать больше информации о том, как механизм сборки
|
||
мусора выполняется в PHP. Но для этого потребуется перекомпилировать PHP,
|
||
чтобы включить код теста производительности и сбора данных. До запуска команды
|
||
<literal>./configure</literal> с параметрами, которые требуются пользователю,
|
||
потребуется установить для переменной окружения <literal>CFLAGS</literal> значение
|
||
<literal>-DGC_BENCH=1</literal>. Следующая последовательность должна сработать:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Пример перекомпиляции PHP для включения теста производительности сборки мусора</title>
|
||
<programlisting role="shell">
|
||
<![CDATA[
|
||
export CFLAGS=-DGC_BENCH=1
|
||
./config.nice
|
||
make clean
|
||
make
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
При повторном запуске приведенного примера кода с двоичным файлом PHP, который
|
||
только что создали, после завершения выполнения PHP выведет следующее:
|
||
</para>
|
||
<para>
|
||
<example>
|
||
<title>Статистика сборки мусора</title>
|
||
<programlisting role="shell">
|
||
<![CDATA[
|
||
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
|
||
]]>
|
||
</programlisting>
|
||
</example>
|
||
</para>
|
||
<para>
|
||
Самую информативную статистику показывает первый блок. Видно,
|
||
что механизм сборки мусора запускался 110 раз, и суммарно освободил
|
||
больше 2 миллионов записей в памяти. Как только механизм сборки мусора
|
||
сработал хотя бы один раз, показатель пика корневого буфера (Root buffer peak)
|
||
будет равняться 10 000.
|
||
</para>
|
||
</sect2>
|
||
|
||
<sect2 xml:id="features.gc.performance-considerations.conclusion">
|
||
<title>Заключение</title>
|
||
<para>
|
||
Сборщик мусора в PHP вызывает замедление работы только
|
||
во время работы алгоритма сборки циклических ссылок, тогда как
|
||
в стандартных скриптах меньшего размера производительность не падает.
|
||
</para>
|
||
<para>
|
||
Когда механизм сборки циклов все-таки запускается
|
||
для стандартных скриптов, объём памяти, которую экономит механизм,
|
||
разрешает одновременно запускать на сервере большее количество скриптов,
|
||
поскольку в целом скрипты занимают не так много памяти.
|
||
</para>
|
||
<para>
|
||
Преимущества заметнее для скриптов, которые работают долго —
|
||
большие наборы тестов или демоны. Новый механизм существенно сокращает утечки памяти
|
||
для приложений, которые работают с расширением <link xlink:href="&url.php.gtk;">PHP-GTK</link>,
|
||
которые часто выполняются дольше, чем скрипты для веба.
|
||
</para>
|
||
</sect2>
|
||
</sect1>
|
||
</chapter>
|
||
<!-- Keep this comment at the end of the file
|
||
vim600: syn=xml fen fdm=syntax fdl=2 si
|
||
vim: et tw=78 syn=sgml
|
||
vi: ts=1 sw=1
|
||
-->
|