Выделение памяти в php
Возвращает количество памяти в байтах, которое было выделено PHP-скрипту на данный момент.
Что такое параметр PHP memory_limit
Итак, важно понимать, что значение PHP memory_limit — это значение для каждого исполняемого скрипта PHP. Можно представить себе это как ограничение скорости для автомобилей при движении по дороге — ограничение действует для каждого автомобиля и составляет конкретное значение — 60 км/ч, к примеру.
See Also
- memory_get_peak_usage() - Returns the peak of memory allocated by PHP
Для чего нужен параметр PHP memory_limit
Параметр PHP memory_limit появился именно для ограничения занимаемой оперативной памяти, которую использую PHP скрипты. Чаще всего проблемы с использованием памяти возникаю в плохо написанных скриптах. И вот для того, чтобы скрипт с допущенной утечкой памяти не «съел» всю доступную память сервера и был внедрен параметр PHP memory_limit.
Пример ошибочной конфигурации
Реальный кейс — подключаемся к серверу у которого 2048Мб (2Гб) оперативной памяти. Кто-то установил memory_limit значение 1024М (1Гб), полагая, что таким образом ограничит использование интерпретатором PHP памяти в 1Гб. В общем-то логично, но, к сожалению — это так не работает. На этом сервере криво был написан скрипт на PHP и памяти он потреблял около 800Мб, что само по себе — ненормально.
Так вот, пока скрипт запускался 1 — 2 раза одновременно — проблем не было, однако, когда на ресурс возросла нагрузка и скрипт запустился 3 или 4 раза одновременно (ну просто к одному и тому же скрипту обратились разные пользователи одновременно) — начались «падения» сервера и ошибки в логах. А так как скрипт потребляет 800Мб — ограничение в 1Гб не было достигнуто, что приводило к краху системы, ведь для запуска 3-х таких скриптов требовалось уже около 2,5Гб Памяти, а в наличии только 2Гб.
Возвращаемые значения
Возвращает количество памяти в байтах.
Return Values
Returns the memory amount in bytes.
Выводы
Повторим еще раз — PHP memory_limit — это параметр для каждого отдельного скрипта PHP, а не ограничение для РНР в целом. И его неправильная настройка рано или поздно приведет к «падению» сайта и его недоступности для клиентов, что очень плохо как для ранжирования в поискоых системах, так и для репутации ресурса.
Для правильной настройки сервера необходимо обладать достаточными компетенциями и опытом соответствующей настройки. И мы крайне не рекомендуем допускать к настройкам сервера сомнительных «специалистов».
Специалисты server [admin] с удовольствием настроят Вам web-сервер так, как нужно.
Examples
// This is only an example, the numbers below will
// differ depending on your system
echo memory_get_usage () . "\n" ; // 36640
$a = str_repeat ( "Hello" , 4242 );
echo memory_get_usage () . "\n" ; // 57960
echo memory_get_usage () . "\n" ; // 36744
Примеры
// Это просто пример, цифры ниже будут
// отличаться в зависимости от вашей системы
echo memory_get_usage () . "\n" ; // 36640
$a = str_repeat ( "Hello" , 4242 );
echo memory_get_usage () . "\n" ; // 57960
echo memory_get_usage () . "\n" ; // 36744
Список параметров
Передача true позволяет узнать реальное количество памяти, выделенной PHP скрипту системой, включая неиспользуемые страницы. Если аргумент не задан или равен false , будет возвращено только количество используемой памяти.
Замечание:
PHP не отслеживает память, которая выделялась не emalloc()
User Contributed Notes 15 notes
To get the memory usage in KB or MB
function convert ( $size )
$unit =array( 'b' , 'kb' , 'mb' , 'gb' , 'tb' , 'pb' );
return @ round ( $size / pow ( 1024 ,( $i = floor ( log ( $size , 1024 )))), 2 ). ' ' . $unit [ $i ];
>
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 );
if ( $mem_usage < 1024 )
echo $mem_usage . " bytes" ;
elseif ( $mem_usage < 1048576 )
echo round ( $mem_usage / 1024 , 2 ). " kilobytes" ;
else
echo round ( $mem_usage / 1048576 , 2 ). " megabytes" ;
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):
// Returns used memory (either in percent (without percent sign) or free and overall in bytes)
function getServerMemoryUsage ( $getPercentage = true )
$memoryTotal = null ;
$memoryFree = null ;
if ( stristr ( PHP_OS , "win" )) // Get total physical memory (this is in bytes)
$cmd = "wmic ComputerSystem get TotalPhysicalMemory" ;
@ exec ( $cmd , $outputTotalPhysicalMemory );
// Get free physical memory (this is in kibibytes!)
$cmd = "wmic OS get FreePhysicalMemory" ;
@ exec ( $cmd , $outputFreePhysicalMemory );
// Find free value
foreach ( $outputFreePhysicalMemory as $line ) if ( $line && preg_match ( "/^1+\$/" , $line )) $memoryFree = $line ;
$memoryFree *= 1024 ; // convert from kibibytes to bytes
break;
>
>
>
>
else
if ( is_readable ( "/proc/meminfo" ))
$stats = @ file_get_contents ( "/proc/meminfo" );
if ( $stats !== false ) // Separate lines
$stats = str_replace (array( "\r\n" , "\n\r" , "\r" ), "\n" , $stats );
$stats = explode ( "\n" , $stats );
// Separate values and find correct lines for total and free mem
foreach ( $stats as $statLine ) $statLineData = explode ( ":" , trim ( $statLine ));
//
// Extract size (TODO: It seems that (at least) the two values for total and free memory have the unit "kB" always. Is this correct?
//
// Total memory
if ( count ( $statLineData ) == 2 && trim ( $statLineData [ 0 ]) == "MemTotal" ) $memoryTotal = trim ( $statLineData [ 1 ]);
$memoryTotal = explode ( " " , $memoryTotal );
$memoryTotal = $memoryTotal [ 0 ];
$memoryTotal *= 1024 ; // convert from kibibytes to bytes
>
// Free memory
if ( count ( $statLineData ) == 2 && trim ( $statLineData [ 0 ]) == "MemFree" ) $memoryFree = trim ( $statLineData [ 1 ]);
$memoryFree = explode ( " " , $memoryFree );
$memoryFree = $memoryFree [ 0 ];
$memoryFree *= 1024 ; // convert from kibibytes to bytes
>
>
>
>
>
if ( is_null ( $memoryTotal ) || is_null ( $memoryFree )) return null ;
> else if ( $getPercentage ) return ( 100 - ( $memoryFree * 100 / $memoryTotal ));
> else return array(
"total" => $memoryTotal ,
"free" => $memoryFree ,
);
>
>
>
function getNiceFileSize ( $bytes , $binaryPrefix = true ) if ( $binaryPrefix ) $unit =array( 'B' , 'KiB' , 'MiB' , 'GiB' , 'TiB' , 'PiB' );
if ( $bytes == 0 ) return '0 ' . $unit [ 0 ];
return @ round ( $bytes / pow ( 1024 ,( $i = floor ( log ( $bytes , 1024 )))), 2 ) . ' ' . (isset( $unit [ $i ]) ? $unit [ $i ] : 'B' );
> else $unit =array( 'B' , 'KB' , 'MB' , 'GB' , 'TB' , 'PB' );
if ( $bytes == 0 ) return '0 ' . $unit [ 0 ];
return @ round ( $bytes / pow ( 1000 ,( $i = floor ( log ( $bytes , 1000 )))), 2 ) . ' ' . (isset( $unit [ $i ]) ? $unit [ $i ] : 'B' );
>
>
// Memory usage: 4.55 GiB / 23.91 GiB (19.013557664178%)
$memUsage = getServerMemoryUsage ( false );
echo sprintf ( "Memory usage: %s / %s (%s%%)" ,
getNiceFileSize ( $memUsage [ "total" ] - $memUsage [ "free" ]),
getNiceFileSize ( $memUsage [ "total" ]),
getServerMemoryUsage ( true )
);
?>
The function getNiceFileSize() is not required. Just used to shorten size in bytes.
[EDIT by danbrown AT php DOT net: This is intended by the author to only be used with PHP 4 < 4.3.2.]
I'd just like to point out that although sandeepc at myrealbox dot com's idea for displaying the current memory usage is a good one, it's perhaps a bad idea to pipe the entire process list through grep. A better performing method would be to select only the process we're interested in:
$pid = getmypid ();
error_log ( 'MEMORY USAGE (% KB PID ): ' . ` ps --pid $pid --no-headers -o%mem,rss,pid `);
?>
True, it's not much of a performance boost, but every bit helps.
I can confirm that this function triggers a garbage collection. I have a script that exceeded 128MB of memory at some point and ended with a fatal error. I was confused, because the script dealt with some large files initially, but the memory load from that point on should have been marginal, and the error occurred at the very end.
Those large files were dealt in a dedicated function and i even used unset() on the variable holding the file after the file was written to disk inside that function. So the memory should have been cleared twice, first after the unset() call, and second once the function ended.
To debug the memory usage, I called memory_get_usage(true) at some points and echo-ed the memory allocation. Just by adding a few echos here and there in the script, the memory usage never exceeded 1MB overhead (on top of the current file size) and the memory error disappeared.
The method sandeepc at myrealbox dot com posted yields larger memory usage, my guess is that it includes all the PHP interpreter/internal code and not just the script being run.
1) Use ps command
MEMORY USAGE (% KB PID ): 0.8 12588 25087 -> about 12MB
2) Use memory_get_usage()
int(6041952) -> about 6MB
This is a function that should work for both Windows XP/2003 and most distrabutions of UNIX and Mac OS X.
return preg_replace ( '/[\D]/' , '' , $output [ 5 ] ) * 1024 ;
>
>else
<
//We now assume the OS is UNIX
//Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4
//This should work on most UNIX systems
$pid = getmypid ();
exec ( "ps -eo%mem,rss,pid | grep $pid " , $output );
$output = explode ( " " , $output [ 0 ]);
//rss is given in 1024 byte units
return $output [ 1 ] * 1024 ;
>
>
>
?>
Note that the description for `memory_get_usage` is different than it's default parameter!
"`int memory_get_usage ([ bool $real_usage = FALSE ] )`
Returns the amount of memory, in bytes, that's currently being allocated to your PHP script. "
Default parameter = `FALSE`
WRONG description: Returns the amount of memory, in bytes, that's currently being allocated to your PHP script.
It must be: Returns the amount of memory, in bytes, that's currently used by your PHP script.
[EDIT by danbrown AT php DOT net: This function will only extend Windows versions of PHP where the server has the required third-party software.]
I was unable to get the previous examples working properly and created code which works at least for me. Enjoy!
// try to use PHP build in function
if( function_exists ( 'memory_get_usage' ) ) <
return memory_get_usage ();
>
// Try to get Windows memory usage via pslist command
if ( substr ( PHP_OS , 0 , 3 ) == 'WIN' )
$resultRow = 8 ;
$resultRowItemStartPosition = 34 ;
$resultRowItemLength = 8 ;
$output = array();
exec ( 'pslist -m ' . getmypid () , $output );
return trim ( substr ( $output [ $resultRow ], $resultRowItemStartPosition , $resultRowItemLength )) . ' KB' ;
// No memory functionality available at all
return 'no value' ;
Разбивка на страницы — это способ управления памятью, выделяемой для пользовательских процессов. Все доступы процессов к памяти являются виртуальными, а преобразование их адресов в адреса физической памяти выполняют ОС и аппаратный MMU.
При разбивке на страницы память делится на блоки фиксированного размера. В Linux на x86/64-платформах размер страниц обычно составляет 4 Кб. Каждый процесс содержит в себе таблицу, в которой хранится информация о соответствии адресов страницы и физической памяти — элемент таблицы страниц (page table entry). Чтобы ОС не лезла в эту таблицу при каждом обращении к памяти (иначе для обработки каждого запроса на обращение к памяти потребуется обращаться к ней дважды), применяется небольшой кэш — буфер ассоциативной трансляции (Translationlookaside Buffer, TLB). Этот аппаратный компонент находится в MMU и работает чрезвычайно быстро и эффективно. Система сканирует TLB с целью поиска записи о соответствии адресов страницы и физической памяти. Если нужной записи там не оказывается, тогда ядру ОС приходится обращаться к памяти, искать нужное соответствие и обновлять информацию в TLB, чтобы получить из памяти нужные нам данные.
Если вы хотите больше узнать об управлении виртуальной памятью, то можете изучить эту публикацию. А пока давайте разберем, как в PHP 7 устроена работа с большими страницами (Huge Page).
Все просто: чем больше размер страницы, тем больше данных в нее можно поместить. Значит, ядро ОС за одно обращение к памяти получает доступ к большему объему данных. Также снижается вероятность промаха в TLB, ведь каждая запись теперь «покрывает» больше данных. Начиная с версии 2.6.20 ядро Linux получило возможность работать с большими страницами (подробнее об этом: раз, два, три). Большая страница обычно в 512 раз больше стандартной: 2 Мб вместо 4 Кб. Чаще всего ядро выполняет так называемое прозрачное выделение больших страниц (transparent huge page mapping): виртуальная память делится на стандартные страницы по 4 Кб, но иногда группа из следующих друг за другом страниц объединяется в одну большую. Обычно это используется при работе с занимающим огромное адресное пространство массивом. Но будьте внимательны: эта память может возвращаться операционной системе маленькими порциями, что приведет к потере огромного объема страницы, а ядру придется откатывать процедуру объединения, снова выделяя 512 страниц по 4 Кб.
Инициировать процедуру объединения может сам пользовательский процесс. Если вы уверены, что сможете заполнить данными всю большую страницу, то лучше запросите у ядра о ее выделении. Наличие больших страниц облегчает управление памятью, ведь ядру приходится просматривать меньше элементов таблицы страниц. К тому же уменьшается количество записей в TLB, да и система в целом будет работать эффективнее и быстрее.
Трудясь над PHP 7, мы потратили много сил на более эффективную работу с памятью. Критически важные внутренние структуры в PHP 7 были переписаны с целью более эффективного использования кэша ЦПУ. В частности, улучшена пространственная локальность, поэтому в кэш помещается больше односвязных данных, а движок реже обращается к памяти. Расширение OPCache теперь имеет больше возможностей по работе с большими страницами.
В мире Unix существует два API для работы с распределением виртуальной памяти. Предпочтительнее использовать функцию mmap(), поскольку она действительно позволяет выделять большие страницы. Также есть функция madvise(), которая лишь дает подсказки (рекомендации) ядру относительно преобразования части памяти в большую страницу, но гарантий никаких.
Прежде чем запрашивать выделение большой страницы, нужно удостовериться, что:
- ваша ОС способна с ними работать,
- есть в наличии свободные большие страницы.
В этом примере доступно 20 больших страниц по 2 Мб. Linux на x86/64-платформе может работать со страницами до 1 Гб, хотя для PHP такой размер не рекомендован, в отличие от СУБД, где возможен выигрыш от больших размеров.
Далее можно использовать API. Чтобы выделить часть памяти под большую страницу, необходимо удостовериться в том, что границы адресного пространства совпадают с границами большой страницы. В любом случае, это нужно делать для повышения эффективности ЦПУ. После этого можно запросить у ядра выделение страницы. В следующем примере выравнивание адресов будет сделано с помощью языка С, а буфер для этой задачи взят из кучи. Ради кроссплатформенной совместимости мы не будем использовать существующие функции для выравнивания, вроде posix_memalign().
Если вы знакомы с языком С, то пояснять особо нечего. Память не освобождается явным образом, поскольку выполнение приложения все равно завершится, да и пример этот нужен лишь для того, чтобы проиллюстрировать идею.
Когда процесс разметил память и практически завершился, можно наблюдать те самые большие страницы, зарезервированные ядром:
Зарезервированы, потому что страница не будет занесена в виртуальную память, пока вы не запишете в нее данные. Здесь 16 страниц помечены как зарезервированные. 16 х 2 Мб = 32 Мб — такой объем памяти мы можем использовать для создания большой страницы с помощью mmap().
Объем кодового сегмента PHP 7 весьма велик. На моей LP64 x86/64-машине он составляет около 9 Мб (отладочная сборка):
В этом примере текстовый сегмент занимает кусок памяти с 00400000 по 00db8000. То есть общий объем бинарного машинного кода PHP составляет больше 9 Мб. Да, PHP развивается, обрастает функциями, и содержит все больше С-кода, преобразованного в машинный код.
Рассмотрим свойства нашего сегмента памяти. Он выделен с помощью традиционных страниц по 4 Кб:
Ядро не использовало прозрачное выделение большой страницы для данного сегмента. Возможно, оно прибегнет к этому позднее, по мере дальнейшего использования процесса с pid8435. Не станем углубляться в вопросы управления ядром большими страницами, однако с помощью OPCache можем перераспределить наш сегмент в большую страницу.
Использование больших страниц в данном случае целесообразно, поскольку кодовый сегмент не меняется в размере и не перемещается при завершении процесса. Наши 9 952 Кб можно уместить в четыре страницы по 2 Мб, а остаток рассредоточить по обычным страницам по 4 Кб.
- вы используете PHP 7;
- ваша система поддерживает большие страницы;
- вы присвоили opcache.huge_code_pages значение 1 (вместо 0);
- и PHP не является модулем вебсервера,
- тогда OPCache сразу после запуска попытается разместить ваш кодовый сегмент в больших страницах. Это делается с помощью функции accel_move_code_to_huge_pages().
OPCache открывает /proc/self/maps и ищет кодовый сегмент памяти. По-другому сделать это не получится, поскольку доступ к подобной информации нельзя получить без явного использования зависимостей ядра. Сегодня procfs используется во всех Unix-системах.
Сканируем файл, находим кодовый сегмент, выравниваем границы в соответствии с адресным пространством большой страницы. Затем вызываем accel_remap_huge_pages() с указанием выровненных границ.
Все достаточно просто. Мы создали новый временный буфер (mem), скопировали в него данные, затем с помощью mmap() попытались распределить выровненный буфер по большим страницам. Если попытка не увенчалась успехом, то можно подсказать ядру с помощью madvise(). После распределения сегмента по страницам копируем данные обратно и возвращаемся.
8 Мб распределены по четырем большим страницам, а 1760 Кб — по стандартным. Мне это дало прирост производительности Zend в 3% при больших нагрузках.
При использовании больших страниц:
- в 512 раз снижается общее количество страниц виртуальной памяти;
- TLB задействуется гораздо активнее, что снижает частоту обращения к памяти;
- можно оптимизировать исполнение машинных инструкций, которые PHP загружает в ЦПУ.
Теперь понятно, каким образом расширение OPCache для PHP 7 помогает повысить производительность системы при использовании теперь уже распространенной техники управления памятью, известной как «большие страницы».
Кстати, ряд СУБД (например, Oracle, PostgreSQL) уже несколько лет используют преимущества больших страниц.
Разбивка на страницы — это способ управления памятью, выделяемой для пользовательских процессов. Все доступы процессов к памяти являются виртуальными, а преобразование их адресов в адреса физической памяти выполняют ОС и аппаратный MMU.
При разбивке на страницы память делится на блоки фиксированного размера. В Linux на x86/64-платформах размер страниц обычно составляет 4 Кб. Каждый процесс содержит в себе таблицу, в которой хранится информация о соответствии адресов страницы и физической памяти — элемент таблицы страниц (page table entry). Чтобы ОС не лезла в эту таблицу при каждом обращении к памяти (иначе для обработки каждого запроса на обращение к памяти потребуется обращаться к ней дважды), применяется небольшой кэш — буфер ассоциативной трансляции (Translationlookaside Buffer, TLB). Этот аппаратный компонент находится в MMU и работает чрезвычайно быстро и эффективно. Система сканирует TLB с целью поиска записи о соответствии адресов страницы и физической памяти. Если нужной записи там не оказывается, тогда ядру ОС приходится обращаться к памяти, искать нужное соответствие и обновлять информацию в TLB, чтобы получить из памяти нужные нам данные.
Если вы хотите больше узнать об управлении виртуальной памятью, то можете изучить эту публикацию. А пока давайте разберем, как в PHP 7 устроена работа с большими страницами (Huge Page).
Все просто: чем больше размер страницы, тем больше данных в нее можно поместить. Значит, ядро ОС за одно обращение к памяти получает доступ к большему объему данных. Также снижается вероятность промаха в TLB, ведь каждая запись теперь «покрывает» больше данных. Начиная с версии 2.6.20 ядро Linux получило возможность работать с большими страницами (подробнее об этом: раз, два, три). Большая страница обычно в 512 раз больше стандартной: 2 Мб вместо 4 Кб. Чаще всего ядро выполняет так называемое прозрачное выделение больших страниц (transparent huge page mapping): виртуальная память делится на стандартные страницы по 4 Кб, но иногда группа из следующих друг за другом страниц объединяется в одну большую. Обычно это используется при работе с занимающим огромное адресное пространство массивом. Но будьте внимательны: эта память может возвращаться операционной системе маленькими порциями, что приведет к потере огромного объема страницы, а ядру придется откатывать процедуру объединения, снова выделяя 512 страниц по 4 Кб.
Инициировать процедуру объединения может сам пользовательский процесс. Если вы уверены, что сможете заполнить данными всю большую страницу, то лучше запросите у ядра о ее выделении. Наличие больших страниц облегчает управление памятью, ведь ядру приходится просматривать меньше элементов таблицы страниц. К тому же уменьшается количество записей в TLB, да и система в целом будет работать эффективнее и быстрее.
Трудясь над PHP 7, мы потратили много сил на более эффективную работу с памятью. Критически важные внутренние структуры в PHP 7 были переписаны с целью более эффективного использования кэша ЦПУ. В частности, улучшена пространственная локальность, поэтому в кэш помещается больше односвязных данных, а движок реже обращается к памяти. Расширение OPCache теперь имеет больше возможностей по работе с большими страницами.
В мире Unix существует два API для работы с распределением виртуальной памяти. Предпочтительнее использовать функцию mmap(), поскольку она действительно позволяет выделять большие страницы. Также есть функция madvise(), которая лишь дает подсказки (рекомендации) ядру относительно преобразования части памяти в большую страницу, но гарантий никаких.
Прежде чем запрашивать выделение большой страницы, нужно удостовериться, что:
- ваша ОС способна с ними работать,
- есть в наличии свободные большие страницы.
В этом примере доступно 20 больших страниц по 2 Мб. Linux на x86/64-платформе может работать со страницами до 1 Гб, хотя для PHP такой размер не рекомендован, в отличие от СУБД, где возможен выигрыш от больших размеров.
Далее можно использовать API. Чтобы выделить часть памяти под большую страницу, необходимо удостовериться в том, что границы адресного пространства совпадают с границами большой страницы. В любом случае, это нужно делать для повышения эффективности ЦПУ. После этого можно запросить у ядра выделение страницы. В следующем примере выравнивание адресов будет сделано с помощью языка С, а буфер для этой задачи взят из кучи. Ради кроссплатформенной совместимости мы не будем использовать существующие функции для выравнивания, вроде posix_memalign().
Если вы знакомы с языком С, то пояснять особо нечего. Память не освобождается явным образом, поскольку выполнение приложения все равно завершится, да и пример этот нужен лишь для того, чтобы проиллюстрировать идею.
Когда процесс разметил память и практически завершился, можно наблюдать те самые большие страницы, зарезервированные ядром:
Зарезервированы, потому что страница не будет занесена в виртуальную память, пока вы не запишете в нее данные. Здесь 16 страниц помечены как зарезервированные. 16 х 2 Мб = 32 Мб — такой объем памяти мы можем использовать для создания большой страницы с помощью mmap().
Объем кодового сегмента PHP 7 весьма велик. На моей LP64 x86/64-машине он составляет около 9 Мб (отладочная сборка):
В этом примере текстовый сегмент занимает кусок памяти с 00400000 по 00db8000. То есть общий объем бинарного машинного кода PHP составляет больше 9 Мб. Да, PHP развивается, обрастает функциями, и содержит все больше С-кода, преобразованного в машинный код.
Рассмотрим свойства нашего сегмента памяти. Он выделен с помощью традиционных страниц по 4 Кб:
Ядро не использовало прозрачное выделение большой страницы для данного сегмента. Возможно, оно прибегнет к этому позднее, по мере дальнейшего использования процесса с pid8435. Не станем углубляться в вопросы управления ядром большими страницами, однако с помощью OPCache можем перераспределить наш сегмент в большую страницу.
Использование больших страниц в данном случае целесообразно, поскольку кодовый сегмент не меняется в размере и не перемещается при завершении процесса. Наши 9 952 Кб можно уместить в четыре страницы по 2 Мб, а остаток рассредоточить по обычным страницам по 4 Кб.
- вы используете PHP 7;
- ваша система поддерживает большие страницы;
- вы присвоили opcache.huge_code_pages значение 1 (вместо 0);
- и PHP не является модулем вебсервера,
- тогда OPCache сразу после запуска попытается разместить ваш кодовый сегмент в больших страницах. Это делается с помощью функции accel_move_code_to_huge_pages().
OPCache открывает /proc/self/maps и ищет кодовый сегмент памяти. По-другому сделать это не получится, поскольку доступ к подобной информации нельзя получить без явного использования зависимостей ядра. Сегодня procfs используется во всех Unix-системах.
Сканируем файл, находим кодовый сегмент, выравниваем границы в соответствии с адресным пространством большой страницы. Затем вызываем accel_remap_huge_pages() с указанием выровненных границ.
Все достаточно просто. Мы создали новый временный буфер (mem), скопировали в него данные, затем с помощью mmap() попытались распределить выровненный буфер по большим страницам. Если попытка не увенчалась успехом, то можно подсказать ядру с помощью madvise(). После распределения сегмента по страницам копируем данные обратно и возвращаемся.
8 Мб распределены по четырем большим страницам, а 1760 Кб — по стандартным. Мне это дало прирост производительности Zend в 3% при больших нагрузках.
При использовании больших страниц:
- в 512 раз снижается общее количество страниц виртуальной памяти;
- TLB задействуется гораздо активнее, что снижает частоту обращения к памяти;
- можно оптимизировать исполнение машинных инструкций, которые PHP загружает в ЦПУ.
Теперь понятно, каким образом расширение OPCache для PHP 7 помогает повысить производительность системы при использовании теперь уже распространенной техники управления памятью, известной как «большие страницы».
Кстати, ряд СУБД (например, Oracle, PostgreSQL) уже несколько лет используют преимущества больших страниц.
Returns the amount of memory, in bytes, that's currently being allocated to your PHP script.
Где изменить и какую величину PHP memory_limit использовать
Настройка этого параметра производится в файле php.ini и имеет вид:
Альтернативно (в случае использования Apache в качестве бекенд-сервера) — можно использовать файл .htaccess и добавить туда директиву:
Мы рекомендуем использовать максимум 128Мб для ограничения PHP memory_limit, а лучше еще меньше. Если скрипту требуется больше 128Мб памяти, то в большинстве случаев требуется ревизия и оптимизация кода скрипта.
Смотрите также
- memory_get_peak_usage() - Возвращает пиковое значение объёма памяти, выделенное PHP
Parameters
Set this to true to get total memory allocated from system, including unused pages. If not set or false only the used memory is reported.
Note:
PHP does not track memory that is not allocated by emalloc()
Как проявляется проблема
Скорее всего в логах ошибок интерпретатора PHP будет что-то вроде:
Fatal error: Allowed memory size of x bytes exhausted (tried to allocate x bytes) in /path/to/php/script
Список параметров
Передача true позволяет узнать реальное количество памяти, выделенной PHP скрипту системой, включая неиспользуемые страницы. Если аргумент не задан или равен false , будет возвращено только количество используемой памяти.
Замечание:
PHP не отслеживает память, которая выделялась не emalloc()
User Contributed Notes 15 notes
To get the memory usage in KB or MB
function convert ( $size )
$unit =array( 'b' , 'kb' , 'mb' , 'gb' , 'tb' , 'pb' );
return @ round ( $size / pow ( 1024 ,( $i = floor ( log ( $size , 1024 )))), 2 ). ' ' . $unit [ $i ];
>
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 );
if ( $mem_usage < 1024 )
echo $mem_usage . " bytes" ;
elseif ( $mem_usage < 1048576 )
echo round ( $mem_usage / 1024 , 2 ). " kilobytes" ;
else
echo round ( $mem_usage / 1048576 , 2 ). " megabytes" ;
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):
// Returns used memory (either in percent (without percent sign) or free and overall in bytes)
function getServerMemoryUsage ( $getPercentage = true )
$memoryTotal = null ;
$memoryFree = null ;
if ( stristr ( PHP_OS , "win" )) // Get total physical memory (this is in bytes)
$cmd = "wmic ComputerSystem get TotalPhysicalMemory" ;
@ exec ( $cmd , $outputTotalPhysicalMemory );
// Get free physical memory (this is in kibibytes!)
$cmd = "wmic OS get FreePhysicalMemory" ;
@ exec ( $cmd , $outputFreePhysicalMemory );
// Find free value
foreach ( $outputFreePhysicalMemory as $line ) if ( $line && preg_match ( "/^1+\$/" , $line )) $memoryFree = $line ;
$memoryFree *= 1024 ; // convert from kibibytes to bytes
break;
>
>
>
>
else
if ( is_readable ( "/proc/meminfo" ))
$stats = @ file_get_contents ( "/proc/meminfo" );
if ( $stats !== false ) // Separate lines
$stats = str_replace (array( "\r\n" , "\n\r" , "\r" ), "\n" , $stats );
$stats = explode ( "\n" , $stats );
// Separate values and find correct lines for total and free mem
foreach ( $stats as $statLine ) $statLineData = explode ( ":" , trim ( $statLine ));
//
// Extract size (TODO: It seems that (at least) the two values for total and free memory have the unit "kB" always. Is this correct?
//
// Total memory
if ( count ( $statLineData ) == 2 && trim ( $statLineData [ 0 ]) == "MemTotal" ) $memoryTotal = trim ( $statLineData [ 1 ]);
$memoryTotal = explode ( " " , $memoryTotal );
$memoryTotal = $memoryTotal [ 0 ];
$memoryTotal *= 1024 ; // convert from kibibytes to bytes
>
// Free memory
if ( count ( $statLineData ) == 2 && trim ( $statLineData [ 0 ]) == "MemFree" ) $memoryFree = trim ( $statLineData [ 1 ]);
$memoryFree = explode ( " " , $memoryFree );
$memoryFree = $memoryFree [ 0 ];
$memoryFree *= 1024 ; // convert from kibibytes to bytes
>
>
>
>
>
if ( is_null ( $memoryTotal ) || is_null ( $memoryFree )) return null ;
> else if ( $getPercentage ) return ( 100 - ( $memoryFree * 100 / $memoryTotal ));
> else return array(
"total" => $memoryTotal ,
"free" => $memoryFree ,
);
>
>
>
function getNiceFileSize ( $bytes , $binaryPrefix = true ) if ( $binaryPrefix ) $unit =array( 'B' , 'KiB' , 'MiB' , 'GiB' , 'TiB' , 'PiB' );
if ( $bytes == 0 ) return '0 ' . $unit [ 0 ];
return @ round ( $bytes / pow ( 1024 ,( $i = floor ( log ( $bytes , 1024 )))), 2 ) . ' ' . (isset( $unit [ $i ]) ? $unit [ $i ] : 'B' );
> else $unit =array( 'B' , 'KB' , 'MB' , 'GB' , 'TB' , 'PB' );
if ( $bytes == 0 ) return '0 ' . $unit [ 0 ];
return @ round ( $bytes / pow ( 1000 ,( $i = floor ( log ( $bytes , 1000 )))), 2 ) . ' ' . (isset( $unit [ $i ]) ? $unit [ $i ] : 'B' );
>
>
// Memory usage: 4.55 GiB / 23.91 GiB (19.013557664178%)
$memUsage = getServerMemoryUsage ( false );
echo sprintf ( "Memory usage: %s / %s (%s%%)" ,
getNiceFileSize ( $memUsage [ "total" ] - $memUsage [ "free" ]),
getNiceFileSize ( $memUsage [ "total" ]),
getServerMemoryUsage ( true )
);
?>
The function getNiceFileSize() is not required. Just used to shorten size in bytes.
[EDIT by danbrown AT php DOT net: This is intended by the author to only be used with PHP 4 < 4.3.2.]
I'd just like to point out that although sandeepc at myrealbox dot com's idea for displaying the current memory usage is a good one, it's perhaps a bad idea to pipe the entire process list through grep. A better performing method would be to select only the process we're interested in:
$pid = getmypid ();
error_log ( 'MEMORY USAGE (% KB PID ): ' . ` ps --pid $pid --no-headers -o%mem,rss,pid `);
?>
True, it's not much of a performance boost, but every bit helps.
I can confirm that this function triggers a garbage collection. I have a script that exceeded 128MB of memory at some point and ended with a fatal error. I was confused, because the script dealt with some large files initially, but the memory load from that point on should have been marginal, and the error occurred at the very end.
Those large files were dealt in a dedicated function and i even used unset() on the variable holding the file after the file was written to disk inside that function. So the memory should have been cleared twice, first after the unset() call, and second once the function ended.
To debug the memory usage, I called memory_get_usage(true) at some points and echo-ed the memory allocation. Just by adding a few echos here and there in the script, the memory usage never exceeded 1MB overhead (on top of the current file size) and the memory error disappeared.
The method sandeepc at myrealbox dot com posted yields larger memory usage, my guess is that it includes all the PHP interpreter/internal code and not just the script being run.
1) Use ps command
MEMORY USAGE (% KB PID ): 0.8 12588 25087 -> about 12MB
2) Use memory_get_usage()
int(6041952) -> about 6MB
This is a function that should work for both Windows XP/2003 and most distrabutions of UNIX and Mac OS X.
return preg_replace ( '/[\D]/' , '' , $output [ 5 ] ) * 1024 ;
>
>else
<
//We now assume the OS is UNIX
//Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4
//This should work on most UNIX systems
$pid = getmypid ();
exec ( "ps -eo%mem,rss,pid | grep $pid " , $output );
$output = explode ( " " , $output [ 0 ]);
//rss is given in 1024 byte units
return $output [ 1 ] * 1024 ;
>
>
>
?>
Note that the description for `memory_get_usage` is different than it's default parameter!
"`int memory_get_usage ([ bool $real_usage = FALSE ] )`
Returns the amount of memory, in bytes, that's currently being allocated to your PHP script. "
Default parameter = `FALSE`
WRONG description: Returns the amount of memory, in bytes, that's currently being allocated to your PHP script.
It must be: Returns the amount of memory, in bytes, that's currently used by your PHP script.
[EDIT by danbrown AT php DOT net: This function will only extend Windows versions of PHP where the server has the required third-party software.]
I was unable to get the previous examples working properly and created code which works at least for me. Enjoy!
// try to use PHP build in function
if( function_exists ( 'memory_get_usage' ) ) <
return memory_get_usage ();
>
// Try to get Windows memory usage via pslist command
if ( substr ( PHP_OS , 0 , 3 ) == 'WIN' )
$resultRow = 8 ;
$resultRowItemStartPosition = 34 ;
$resultRowItemLength = 8 ;
$output = array();
exec ( 'pslist -m ' . getmypid () , $output );
return trim ( substr ( $output [ $resultRow ], $resultRowItemStartPosition , $resultRowItemLength )) . ' KB' ;
// No memory functionality available at all
return 'no value' ;
В рамках проекта server [admin] мы занимаемся настройкой и администрированием web-серверов. И, конечно, к нам обращаются за решением проблем с уже существующими, т.н. «боевыми» серверами.
Мы часто находим ошибки в конфигурации сервера, особенно если до нас на сервере работали разного рода «специалисты». Одной из наиболее частых проблем является неправильное значение параметра PHP memory_limit.
Читайте также: