php memory get peak usage
memory_get_peak_usage() with «real usage»
If the real_usage argument is set to true the PHP DOCS say it will get the real size of memory allocated from system. If it’s false it will get the memory reported by emalloc()
I want to know how close was the script to hit that limit.
5 Answers 5
Ok, lets test this using a simple script:
Read this question for more information.
Introduction
You should use memory_get_usage(false) because what you want is memory used not memory allocated.
Whats the Difference
Your Google Mail might have allocated 25MB of storage for you but it does not mean that is what you have used at the moment.
This is exactly what the PHP doc was saying
Set this to TRUE to get the real size of memory allocated from system. If not set or FALSE only the memory used by emalloc() is reported.
Both argument would return memory allocated relative to the memory limit but the main difference is:
memory_get_usage(false) give the memory used by emalloc() while memory_get_usage(true) returns milestone which can be demonstration here Memory Mile Store
I want to know how close was the script to hit that limit.
As far as i know the only way i can check memory used for a variable or specific section of PHP is:
See Explanation, but if you are in a loop or recursive function you can use maximum memory usage to estimate safely when memory peek would be reached.
Example
This may still fail
If the memory to process this request is grater than the memory available the script would fail.
Conclusion
Its not a perfect solution but check for memory at interval and if its exceed peek (eg 90%) exit instantly and leave the fancy stuff
memory_get_usage
(PHP 4 >= 4.3.2, PHP 5, PHP 7, PHP 8)
memory_get_usage — Возвращает количество памяти, выделенное для PHP
Описание
Возвращает количество памяти в байтах, которое было выделено PHP-скрипту на данный момент.
Список параметров
PHP не отслеживает память, которая выделялась не emalloc()
Возвращаемые значения
Возвращает количество памяти в байтах.
Примеры
Пример #1 Пример использования memory_get_usage()
// Это просто пример, цифры ниже будут
// отличаться в зависимости от вашей системы
Смотрите также
User Contributed Notes 15 notes
To get the memory usage in KB or MB
echo convert ( memory_get_usage ( true )); // 123 kb
?>
Note, that the official IEC-prefix for kilobyte, megabyte and so on are KiB, MiB, TiB and so on.
At first glance this may sound like «What the hell? Everybody knows, that we mean 1024 not 1000 and the difference is not too big, so what?». But in about 10 years, the size of harddisks (and files on them) reaches the petabyte-limit and then the difference between PB and PiB is magnificent.
Better to get used to it now. 🙂
To get the memory usage in KB or MB
function echo_memory_usage () <
$mem_usage = memory_get_usage ( true );
memory_get_usage() is used to retrieve the memory allocated to PHP only (or your running script). But intuitively, many people expect to get the memory usage of the system, based on the name of the function.
So if you need the overall memory usage, following function might be helpful. If retrieves the memory usage either in percent (without the percent sign) or in bytes by returning an array with free and overall memory of your system. Tested with Windows (7) and Linux (on an Raspberry Pi 2):
//
// Extract size (TODO: It seems that (at least) the two values for total and free memory have the unit «kB» always. Is this correct?
//
?>
The function getNiceFileSize() is not required. Just used to shorten size in bytes.
Управление памятью в PHP
Всем привет. Сегодня разговор пойдёт о том, каким образом в PHP реализовано хранение данных в памяти и о том, что же на самом деле скрывает за собой довольно простой код. В данной статье я хотел бы больше показать на практике результаты работы разного кода, опуская многие детали внутренней реализации. На мой взгляд, для разработчика на языке PHP более приоритетны знания о том, как писать код на PHP, нежели всё внутреннее устройство интерпретатора. Ну а заинтересовавшиеся данной темой люди легко нагуглят материалы по данной теме. Итак, приступим.
Функции для анализа памяти
В первую очередь стоит познакомиться с некоторыми функциями, позволяющими узнать об использовании памяти. Начнём с функции memory_get_usage($real_usage = null).
Эта функция возвращает количество памяти в байтах, выделенной скрипту в данный момент. По умолчанию (без передачи параметров), она возвращает количество памяти, запрошенное процессом для его работы:
При передаче ей единственно возможного аргумента типа boolean, равного true, функция вернёт реально выделенное количество памяти, которое всегда больше, чем запрошенное значение:
Поиграемся немного с этими функциями, напишем небольшой скрипт, который будет сначала забивать память какими-либо данными, а потом уничтожать их:
Результат выполнения данного скрипта:
Requested: 343 KB
Allocated: 2048 KB
Requested: 10583 KB
Allocated: 12288 KB
Requested: 343 KB
Allocated: 2048 KB
Как мы видим, в процессе работы скрипта количество запрашиваемой и выделенной памяти сначала увеличилось, а затем вернулось в исходное состояние.
Рассмотрим также функцию memory_get_peak_usage($real_usage = null)
Она возвращает максимальный объем памяти в байтах, который был выделен PHP скрипту за всё прошедшее время выполнения до данного момента. Как и у предыдущей рассмотренной нами функции у неё есть единственный аргумент, имеющий такой же смысл. Дополним наш скрипт вызовом этой функции в конце:
Теперь вывод будет таким:
Requested: 344 KB
Allocated: 2048 KB
Requested: 10584 KB
Allocated: 12288 KB
Requested: 344 KB
Allocated: 2048 KB
Peak requested: 10584 KB
Peak allocated: 12288 KB
Функция отработала ожидаемым образом. Её можно использовать в конце работы скрипта как вариант быстрого анализа эффективности кода по памяти.
Что происходит на самом деле?
Итак, мы научились узнавать об использовании памяти. Рассмотрим теперь более детально следующий код:
Чтобы понять, как он работает, нам всё же придётся немного углубиться во внутреннюю реализацию языка PHP. Все значения переменных являются структурой zval. Структура zval состоит из четырех полей. Не будем заострять своё внимание на том, как именно это всё устроено. Нам лишь достаточно знать о том, что это за поля, и для чего они нужны. Итак, эти поля:
В первой строке нашего кода происходит создание переменной. В этот момент в куче выделяется память под значение, создаётся структура zval и заполняется указанным значением 123. Затем в специальном месте, называемом таблицей символов, создаётся имя данной переменной $var. Наконец, устанавливается связь между именем переменной и значением в памяти. Теперь переменная $var ссылается на значение 123.
В данный момент поля структуры zval имеют следующие значения:
Что же происходит, когда мы вызываем unset($var)? В таблице символов удаляется имя $var, после чего в структуре zval в поле число ссылок устанавливается значение 0. Таким образом, на это значение больше не ссылается ни одно имя. PHP, видя это, уничтожает данное значение, освобождая таким образом занимаемую им память.
Что произойдёт, если вместо unset, мы присвоим данной переменной значение null? Произойдёт разрыв связи между именем $var и её значением. $var перестаёт ссылаться на какое-либо значение. Теперь $var – это просто имя, и ничего кроме. Число ссылок на значение в этом случае тоже станет равно 0 и оно будет уничтожено.
Ссылки
Рассмотрим следующий код:
В первой строке мы создаём переменную и присваиваем ей значение. Тут всё понятно, это мы проходили.
На следующей строке переменной $var2 присваивается значение $var1. Однако, внутри не всё так просто, как кажется. Мы полагаем, что в куче будет выделена память, значение скопировано, и новое имя начнёт указывать на новое созданное значение. Это не так 🙂
На самом деле, после выполнения второй строки имя $var2 будет указывать всё ещё на тот же zval. При этом у этого zval в поле число ссылок теперь будет число 2. Прошу обратить внимание, что под ссылками мы сейчас подразумеваем не ссылки PHP, которые начинаются со знака &, а связь между именем и значением. Больше никаких изменений в этом zval на данный момент не произойдёт.
Перейдём к третьей строке. А вот теперь будет создан новый zval! Назовём его zval2, а предыдущий — zval1. Рассмотрим теперь, что же именно произошло. А произошло следующее: была выделена память под значение 12345 и создан новый zval (zval2) с этим значением, имя $var1 теперь ссылается на zval2. $var2 продолжает ссылаться на zval1, число ссылок на zval1 уменьшилось и теперь равняется 1. Этот механизм называется copy-on-write. Благодаря ему PHP создаёт новое значение только в тот момент, когда это действительно необходимо. Надеюсь, тут всё понятно.
Теперь рассмотрим следующий код:
и результат его работы:
Рассмотрим вторую строку. Благодаря символу & переменной $var2 происходит присваивание по ссылке значения переменной $var1. В результате этого появляется лишь ещё одно имя, ссылающееся на то же значение. И теперь пришла пора поговорить о четвёртом поле в структуре zval, которое в примерах выше всегда было равно 0. Здесь-то оно и превращается в единицу. И значения полей полученного zval становятся следующими:
Благодаря этому флагу PHP определяет, что этот zval не должен быть скопирован перед изменением, а должно быть изменено лишь его значение.
В результате этого, при выполнении кода в третьей строке, значение, на которое ссылается имя $var1 будет заменено на 12345. На это же значение ссылается имя $var2. В результате этого при чтении значения $var2 мы получим то же значение — 12345.
Теперь, если сделать unset($var1), значение не удалится, оно будет по-прежнему доступно по имени $var2. В zval при этом число ссылок уменьшится до 1, а поле, означающее что значение является использующимся по ссылке станет нулевым.
Передача значений в PHP
Теперь же давайте поговорим о том, как реализована передача значений в функцию, а также о передаче и присваивании объектов и массивов, ведь они, как Вы уже наверное догадались, ведут себя несколько иначе.
Итак, начнём. Помните в прошлой статье мы говорили о таблице символов, в которой хранятся имена переменных? Так вот таких таблиц на самом деле может быть несколько.
Давайте рассмотрим следующий код:
В данном случае одна из этих таблиц предназначена для хранения имён в глобальной области видимости, а вторая будет хранить имена, использующиеся внутри функции func().
Что же произойдёт в момент вызова функции func()? А произойдёт нечто похожее на присваивание, которое мы уже рассматривали. В момент передачи аргумента в функцию произойдёт создание нового имени в таблице символов функции, это имя в данный момент будет ссылаться на то же значение, что и глобальная переменная $var.
Структура zval при этом сейчас содержит информацию о двух ссылках на данное значение. И лишь в момент модификации переменной внутри функции значение будет скопировано и изменено, а имя из таблицы символов функции начнёт ссылаться на это новое значение.
Как мы видим, при передаче в функцию происходит нечто похожее на процесс присваивания переменной, и снова в действии механизм copy-on-write.
Рассмотрим теперь следующий пример:
От предыдущего кода отличается лишь наличием знака & перед аргументом функции. Рассмотрим, что происходило со значением zval. После определения глобальной переменной $x происходит её передача в функцию по ссылке. Число ссылок на значение установилось равным двум, а флаг, говорящий о том, что значение используется по ссылке, даёт PHP понять, что перед модификацией значение не нужно копировать, а стоит изменять непосредственно его. В общем, история та же, что и с простым присваиванием по ссылке.
Объекты
Рассмотрим такой код:
Вывод будет следующим:
После передачи объекта в функцию и изменению его свойства внутри неё, мы видим что свойство объекта изменилось и вне функции. Отсюда, как Вы уже догадались, можно сделать вывод, что объекты всегда передаются по ссылке, независимо от того, был ли знак & перед аргументом, или нет. Да и наверняка Вы уже до этого слышали, что объекты в PHP всегда передаются по ссылке. Так вот это не так 🙂
Но мы же только что видели обратное, как это не так? Объясняю. Видели на самом деле мы лишь изменение свойства объекта, а не объекта как такового. Дело в том, что значение объекта в PHP представляет собой лишь идентификатор объекта, и этот идентификатор уже используется для поиска данных этого объекта. Таким образом в момент передачи в функцию объекта поля zval принимают следующие значения:
А в момент обращения к свойству. объекта происходит поиск свойства по идентификатору объекта и его модификация. Значение самого объекта при этом остаётся неизменным. Проверить это можно следующим кодом:
Вывод будет следующим:
Как видим, объект не изменился. А всё дело в том, что внутри функции в момент присваивания имени из таблицы символов функции другого значения произошло создание нового значения, а у zval объекта число ссылок уменьшилось до 1.
Если теперь всё же добавить & перед аргументом функции:
То вывод будет следующим:
Объяснять, думаю, не нужно. Стандартное поведение при передаче по ссылке, рассмотренное ранее.
Такое поведение объектов, когда при передаче в функцию или присваивании переменной мы можем работать с его свойствами, не производя при этом его копирования позволяет реализовать в PHP эффективное внедрение зависимостей (dependency injection).
Пример:
Здесь в конструкторе класса Db происходит передача объекта класса Config. Мы можем использовать его свойства (как читать, так и изменять) не выполняя при этом копирования и не расходуя память. Да, вот такой вот PHP умный и хороший.
Стоит отметить, что иногда нам может потребоваться скопировать объект, и работать с его копией, внося изменения только в ней. Для этого используется конструкция clone.
Использоваться она может так:
Или даже непосредственно при передаче в функцию:
Массивы
После выполнения данного кода будет создан новый массив $arr2, содержащий в себе элементы $arr1, умноженные на 2. При этом в момент выполнения функции значения массива $arr1 копировались и в итоге заняли такое же количество памяти, что и исходный массив.
Данный код пройдётся по каждому значению $arr1 и изменит непосредственно его значение. С точки зрения производительности по памяти, этот вариант более предпочтителен.
Аналогично можно поступить и с foreach. Использовать элементы массива по значению:
Циклические ссылки и сборщик мусора в PHP
В прошлых частях урока мы рассмотрели структуру zval, разобрались как именно происходит присваивание и передача в функцию, рассмотрели разницу для разных типов. В предыдущих уроках мы рассматривали ситуации, когда вместе с удалением всех имён для какого-либо значения происходило уменьшение числа ссылок на значение до 0. В результате чего значение благополучно удалялось. Однако так происходит далеко не всегда, и об этих ситуациях мы сейчас и поговорим.
Для начала хотелось бы познакомить вас с функцией xdebug_debug_zval($var_name);
Она становится доступной после включения расширения Xdebug.
Рассмотрим следующий пример:
Результат будет следующим:
Помните, в прошлых статьях мы говорили о полях структуры zval, одно из которых отвечает за количество ссылок на значение (не ссылок через &, а связей между именем и значением), а второе говорит о том, используется ли значение по ссылке (здесь уже в том смысле, когда мы используем &).
Циклические ссылки
Так вот о чём это я начал говорить в начале. Как возможна ситуация, когда при удалении всех имён, ссылающихся на значение, число ссылок на это значение останется больше 0 и значение продолжит оставаться в памяти.
Напишем код, реализующий следующее: добавить в массив новым элементом самого себя и вывести получившееся значение zval.
Получится следующее:
a: (refcount=2, is_ref=1)=array (0 => (refcount=2, is_ref=1)=…)
Теперь, если мы сделаем unset($a), refcount уменьшится до 1, значение перестанет быть доступным нам по какому-либо имени и повиснет в памяти. К сожалению, проверить это с помощью только что ставшей известной для нас функции невозможно — имени-то нет. Попробуем для проверки этого заявления прибегнуть к изученной нами ранее функции memory_get_usage(). Для этого поместим в массив элемент, представляющий из себя строку из 100 000 символов. Этого будет достаточно, чтобы заметить изменения в памяти. Для создания такой строки прибегнем к функции str_repeat(). Код получится следующим:
Как мы видим, память не освободилась. Попробуем не добавлять в массив элемент, ссылающийся на этот массив:
Здесь мы видим, что размер запрашиваемой скриптом памяти вернулся к начальному значению. В первом же случае этого не произошло. Так как мы при этом не можем удалить эти «неиспользуемые» данные, то здесь имеет место быть утечка памяти. Примеры, подобные данному встречаются в коде довольно часто. Особенно когда речь идёт об объектах — во многих местах происходит их неявное использование по ссылке, в результате чего один объект в одном из своих свойств может начать содержать ссылку на себя самого.
Как правило, PHP используется непосредственно «для сайтов» и после завершения запроса эти данные будут удалены. Если такая утечка произойдёт в паре мест, то в большинстве случаев ничего ужасного не произойдёт. Однако если это долгоживущий скрипт, запущенный, например в CLI-режиме, то это может привести к выжиранию всей доступной памяти. К счастью, в PHP есть сборщик мусора, или по-английски «garbage collector«.
Сборщик мусора (garbage collector)
По умолчанию сборщик мусора всегда включён. Это задаётся директивой zend.enable_gc в файле php.ini.
Он вызывается… На самом деле это довольно долго и сложно объяснять. Для тех, кто всё же хочет узнать прямо сейчас — прошу сюда.
Так вот, скажу я Вам, он сам прекрасно знает, когда приходит его время. Мы же остановимся на практическом варианте и посмотрим на него в действии. Итак, этот механизм вызывается при определённых условиях и занимается тем, что удаляет ненужные значения, возникшие в результате работы с циклическими ссылками. Напишем код, в бесконечном цикле которого будут создаваться объекты, содержащие ссылки на самих себя с последующим удалением их имён. На каждой тысячной итерации будем выводить размер используемой памяти. Код:
603 KB
790 KB
978 KB
1165 KB
1353 KB
1540 KB
1728 KB
1915 KB
2103 KB
2290 KB
603 KB
.
Как мы видим, размер используемой памяти некоторое время возрастает, а затем возвращается к исходному — это сборщик мусора в деле. Круто, да?
А теперь сделаем так, чтобы объект содержал в себе приличный объём данных:
799 KB
98736 KB
196674 KB
294611 KB
392549 KB
490486 KB
588424 KB
686361 KB
784299 KB
882236 KB
799 KB
98736 KB
…
Вроде бы всё хорошо, однако, если Вы запустите этот скрипт у себя, то увидите ощутимые фризы в работе в момент работы garbage collector’а, где-то на секунду выполнение скрипта остановится. Это время напрямую зависит от объема высвобождаемой памяти. И в предыдущем варианте, где объекты были довольно малы работа garbage collector’а на первый взгляд незаметна. Однако, работа скрипта всё же будет на некоторое время остановлена. В этом его минус, за всё приходится платить. Как правило, Вы вряд ли от этого пострадаете, однако, об этом следует помнить, особенно при работе с большими данными. Если есть фризы и для Вас это критично — Вы знаете в какую сторону теперь копать.
Спасибо за прочтение, желаю Вам всего хорошего 😉
Работа с памятью (и всё же она есть)
Существует распространенное мнение, что «рядовому» PHP разработчику практически не нужно заботиться об управлении памятью, однако «заботиться» и «знать» всё же немного разные понятия. Попытаюсь осветить некоторые аспекты управлению памятью при работе с переменными и массивами, а также интересные «подводные камни» внутренней оптимизации PHP. Как вы сможете убедиться, оптимизация это хорошо, но если не знать как именно она «оптимизирует», то можно столкнуться с «неочевидными граблями», которые могут вас заставить изрядно понервничать.
Общие сведения
Небольшой ликбез
Переменная в PHP как бы состоит из двух частей: «имени«, которое хранится в hash_table symbol_table, и «значения«, которое хранится в zval контейнере.
Такой механизм позволяет создавать несколько переменных ссылающихся на одно значение, что в отдельных случаях позволяет оптимизировать потребление памяти. О том, как это выглядит на практике будет написано далее.
Наиболее частыми элементами кода, без которых сложно себе представить более менее функциональный скрипт, являются следующие моменты:
— создание, присвоение и удаление переменных (чисел, строк и т.п.),
— создание массивов и их обход (в качестве примера будет использована функция foreach),
— передача и возврат значений для функций/методов.
Именно об этих аспектах работы с памятью и будет последующее описание. Получилось достаточно объемно, но ничего мега-сложного не будет и всё будет достаточно просто, очевидно и с примерами.
Первый пример работы с памятью
И простой первый пример теста потребления памяти для строки:
include ( ‘func.php’ ) ;
echo «String memory usage test.\n\n» ;
$base_memory_usage = memory_get_usage ( ) ;
$base_memory_usage = memory_get_usage ( ) ;
Примечание: несомненно код является неоптимизированным с точки зрения работоспособности, но в данном случае нам исключительно важна наглядность потребления памяти, для которой и реализовано данное представление.
Результат кода вполне очевиден:
Start
Bytes diff: 0
String value setted
Bytes diff: 15448
String value unsetted
Bytes diff: 0
Тот же самый пример, но вместо unset($a) используем $a=null;:
Start
Bytes diff: 0
String value setted
Bytes diff: 15448
String value set to null
Bytes diff: 76
Как видите, переменная не была полностью уничтожена. Под нее остается выделенным еще 76 байт.
Достаточно прилично, если учесть, что ровно столько же выделяется и под переменные типа boolean, integer, float. Речь идет не об объеме памяти, выделяемой под значение переменной, а о полном потреблении памяти для хранения сведений о присвоенной переменной (zval контейнер со значением и само имя переменной).
Так что если вы хотите освободить память при помощи присвоения, то не является принципиальным присвоение именно null значения. Выражение $a=10000; даст тот же результат для расхода памяти.
В документации PHP сказано, что приведение к null уничтожит переменную и ее значение, однако, по данному скрипту видно что это не так, что собственно является багом (документации).
Зачем использовать присвоение null, если можно unset()?
Присвоение — это присвоение, (спасибо КО), то есть изменяется значение переменной, соответственно, если новое значение требует меньше памяти, то она высвобождается сразу, однако это требует вычислительных ресурсов (пусть и сравнительно немного).
unset() в свою очередь освобождает память, выделенную под имя переменной и ее значение.
Отдельно стоит упомянуть момент, что unset() и присвоение null совершенно по разному работают со ссылками на переменные. Unset() уничтожит только ссылку, в то время как присвоение null изменит значение, на которое ссылаются имена переменных, соответственно все переменные станут ссылаться на значение null.
Примечание:
Встречается заблуждение, что unset() является функцией, однако, это не верно. unset() — это языковая конструкция (как например if), о чем прямо сказано в документации, соответственно ее нельзя использовать для обращения через значение переменной:
Немного дополнительной информации для праздных размышлений (при изменении примера выше):
$a = array();
выделит 164 байта, unset($a) всё вернет.
class A < >
$a = new A();
выделит 184 байта, unset($a) всё вернет.
$a = new stdClass();
выделит 272 байта, но после unset($a) «утекут» 88 байт (куда именно и почему они утекли, мне пока не удалось выяснить).
Пока приведенные примеры не являются критичными в плане потребления памяти, так как строковые и числовые значения достаточно очевидно хранятся и обрабатываются. Всё становится значительно хуже, когда в ход идут массивы (объекты тоже имеют целый ряд особенностей, однако для этого уже потребуется отдельная статья).
Массивы
Массивы в PHP «съедают» достаточно памяти, и именно в них как правило хранят значительные объемы данных при обработке, поэтому следует очень аккуратно относиться к работе с ними. Однако, работа с массивами в PHP имеет свои «прелести оптимизации» и об одном из таких моментов, связанных с потреблением памяти, стоит упомянуть.
? php
include ( ‘func.php’ ) ;
echo «Array memory usage example.» ;
$base_memory_usage = memory_get_usage ( ) ;
$base_memory_usage = memory_get_usage ( ) ;
Посмотрите на вывод:
Array memory usage example.Base usage.
Bytes diff: 0
Array is set.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 77632
In FOREACH cycle.
Bytes diff: 93032
In FOREACH cycle.
Bytes diff: 108432
In FOREACH cycle.
Bytes diff: 123832
Usage right after FOREACH.
Bytes diff: 61940
Array unset.
Bytes diff: 0
Получается, что в последней итерации цикла foreach в данном случае потребление массивом памяти возросло в два раза, хотя по самому коду это не очевидно. Но сразу после цикла, потребление памяти вернулось к прежнему значению. Чудеса да и только.
Причиной тому является оптимизация использования массива в цикле. На время работы цикла, при попытке изменить исходный массив, неявно создается копия структуры массива (но не копия значений), которая и становится доступной по завершению цикла, а исходная структура уничтожается. Таким образом, в вышеприведенном примере, если вы присваиваете новые значения исходному массиву, то они не будут заменены сразу, а для них будет выделена отдельная память, которая будет возвращена по выходу из цикла.
Этот момент очень легко пропустить, что может привести к значительному потреблению памяти на время работы цикла с большими массивами данных, например при выборке из БД.
Дополнение от пользователя zibada (в кратце):
Важно учесть, что выделение памяти под новый «временный массив» в случае внесения изменений, произойдет единовременно для всей структуры массива, но отдельно для каждого изменяемого элемента. Таким образом, если имеется массив с большим количеством элементов, (но не обязательно с большими значениями), то единовременное потребление памяти при таком копировании будет существенно.
Коварный пример №2
Чуть-чуть изменим код.
Сам код цикла мы никак не меняли, единственное что мы изменили, это увеличили счетчик ссылок на исходный массив, но это в корне поменяло работу цикла:
Bytes diff: 0
Array is set.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 61988
In FOREACH cycle.
Bytes diff: 61988
In FOREACH cycle.
Bytes diff: 61988
In FOREACH cycle.
Bytes diff: 61988
Usage right after FOREACH.
Bytes diff: 61940
Array unset.
Bytes diff: 0
Bytes diff: 0
Array is set.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 61940
In FOREACH cycle.
Bytes diff: 61940
Usage right after FOREACH.
Bytes diff: 61940
Array unset.
Bytes diff: 0
Передача по ссылке или передача через копирование
Рассмотрим случай, «что делать» если требуется передать в метод или функцию (или вернуть из них), какое-либо очень большое значение. Первым очевидным решением обычно рассматривают использование передачи/возвращения по ссылке.
Однако в документации по PHP сказано: Не используйте возврат по ссылке для увеличения производительности. Ядро PHP само занимается оптимизацией.
Попытаемся разобраться в том, что же это за «оптимизация».
Для начала самый простой пример (пока без передачи аргументов):
Start
Bytes diff: 0
String value setted
Bytes diff: 15496
String value unsetted
Bytes diff: 0
В результате получим вывод:
Bytes diff: 0
String value setted
Bytes diff: 30896
String value unsetted
Bytes diff: 0
Данная оптимизация действует для конкретных значений, коими также являются и отдельные значения массива.
Чтобы это лучше понять, взглянем на пример ниже:
Данный пример даст выход:
Bytes diff: 0
String value setted
Bytes diff: 46704
String value unsetted
Bytes diff: 0
Всё выше описанное действует аналогично и для передачи/возврата значений через «оптимизированное копирование» внутрь/из функций и методов. Если внутри метода вы никак не «трогаете» переданное значение, то для него не будет выделена отдельная область памяти (память будет выделена только под имя переменной, чтобы связать ее со значением). Если же вы передаете «через копирование» и изменяете значение внутри метода, то перед попыткой сделать изменение уже будет создана действительная полная копия значения.
Таким образом PHP действительно избавляет от необходимости использовать передачу по ссылке для оптимизации использования памяти. Передача по ссылке имеет практическое значение только если исходное значение требуется изменить с отображением этих изменений извне метода.
echo «Array memory usage example.» ;
$base_memory_usage = memory_get_usage ( ) ;
$base_memory_usage = memory_get_usage ( ) ;
Как видно из примера, в функции не была создана копия массива, несмотря на то, что фактически идет передача значения через копирование. И даже частичная модификация переданного массива не создала полноценную копию, а выделила память только под новые значения.
Исключительно в познавательных целях, стоит обратить внимание на эти два значения:
Примечание:
В PHP5 (в отличие от PHP4), все объекты по-умолчанию передаются по ссылке, хотя по факту, это неполноценная ссылка. См. эту статью.
Краткие выводы
Несомненно приведенные примеры оптимизации использования памяти в PHP лишь «капля в море», однако они описывают самые частые случаи, когда имеет смысл задуматься о том, какой код выбрать чтобы оптимизировать расход памяти и избавить себя от лишней головной боли.
Отдельно стоило бы затронуть механизм расходования и оптимизации памяти при использовании объектов, однако ввиду обилия возможных примеров этот момент требует отдельной статьи. Возможно когда-нибудь.
PS: Можно было бы разбить это на несколько статей, но не вижу в этом смысла, так как подобную информацию лучше всё же хранить «вместе». Полагаю тем, кому данная информация несет практический смысл, так будет удобнее. Тестировалось на PHP 5.3.2 (Ubuntu 32bit), так что ваши значения по выделенным байтам могут отличаться.
UPD
В основной части статьи не был освещен важный момент.
Если есть переменная на которую создана ссылка, то при ее передаче в функцию в качестве аргумента она будет скопирована сразу, то есть не будет применена copy-on-write оптимизация.
Пример: