LINUX.ORG.RU

Как считается память, используемая php-скриптом?

 ,


0

1

Всем доброго времени суток.

Такой вопрос - после небольших изменений в моем скрипте php начал ругаться на то, что я пытаюсь выделить больше память, чем разрешено:

Fatal error: Allowed memory size of 8388608 bytes exhausted

Но если выводить количество используемой памяти через memory_get_usage (true) и memory_get_peak_usage (true), то больше 2 с копейками метров там нет..

В связи с этим вопрос - php суммирует всю выделенную память и следит, чтобы она не превысила установленное значение? Или все-таки у меня реально кривой скрипт?

дада, исходники, а лучше бинарники сразу. а если серьезно, то начни с комментариев в доках к этим функциям на сайте php.net, потом гугол. если читать исходники ко всему, чем пользуешься, можно стать хорошим сишником-инженером, но хорошим программером не стать

heisenberg ★★ ()
Ответ на: комментарий от heisenberg

Да я к тому, что не понятно, к каким функциям комментарии читать.. Про memory_get_usage прочитал, но меня-то изначально не это интересовало, а то, как ведется подсчет выделенной памяти.

Я просто читаю несколько файлов в цикле с помощью

$arr = file ($filename, $flags);
и получаю объемные массивы.

Вроде как по окончанию работы с массивом делаю unset, но все-равно вылезает ошибка (см. первый пост). Вот и возник вопрос, как идет учет этой выделенной памяти... Если считается вся выделенная за время работы память (без учета того, что она может освобождаться), то это одно дело, а если там подсчитывается текущий объем выделенной памяти, то совсем другое...

Собственно, это и хотел узнать. И комментарии к каким функциям нужно читать, в данном случае не совсем понятно...

solovey ★★ ()
Ответ на: комментарий от solovey

Вроде как по окончанию работы с массивом делаю unset, но все-равно вылезает ошибка (см. первый пост). Вот и возник вопрос, как идет учет этой выделенной памяти... Если считается вся выделенная за время работы память (без учета того, что она может освобождаться), то это одно дело

ЕМНИП должна считаться только выделенная, но этот phpшный аллокатор жутко глючит на массивах. Потому и отсылаю к сырцам твоей версии... А вообще, как уже сказали, читай построчно... И пиши тоже.

drBatty ★★ ()
Ответ на: комментарий от byss

Построчное чтение тормозит жутко. Эта фигня на одноплатнике крутится. Там результаты по 10 секунд минимум формируются. Пользователи этого не вынесут...

solovey ★★ ()
Ответ на: комментарий от drBatty

Изначально как-раз построчно и читал. Решил оптимизировать на свою голову...

solovey ★★ ()
Ответ на: комментарий от ukr_unix_user

Да, я в курсе.... А под consume подразумевается суммарное, или текущее. Вот в чем вопрос...

Так-то понятно, что можно изменить на 128М и жить без ругани от php... Просто интересно стало

solovey ★★ ()
Ответ на: комментарий от solovey

Построчное чтение тормозит жутко. Эта фигня на одноплатнике крутится. Там результаты по 10 секунд минимум формируются. Пользователи этого не вынесут...

тогда меняйте ЯП, в вашем интерпретаторе похоже кривое построчное чтение, ибо принципиально на обычном железе разницы быть не должно (и нету), но у вас необычное железо и очевидно не обычный php...

drBatty ★★ ()
Ответ на: комментарий от solovey

Так-то понятно, что можно изменить на 128М и жить без ругани от php... Просто интересно стало

у вас похоже unset вообще не работает, не освобождает память. Хотя должна. Очевидно проблема реализации.

drBatty ★★ ()
Ответ на: комментарий от drBatty

а это идея... Может так и поступлю в более поздней версии

solovey ★★ ()
Ответ на: комментарий от solovey

Построчное чтение тормозит жутко.

Ну так поблочно читать. Скажем, по десятку килобайт.

KRoN73 ★★★★★ ()
Ответ на: комментарий от drBatty

Очевидно проблема реализации.

Вообще, в теме не прозвучала версия PHP. А то они меняли сборщик мусора недавно, обучив его удалять кольцевые ссылки. Ну и, вообще, доработав.

...

Впрочем, я и на старых версиях PHP делал демонов, активно работающих с памятью — на очередях и форках. Основной процесс получает команду и порождает форк. Который уже может с памятью делать что угодно. Когда он закончит работу, память за ним будет подчищена уже системой. Заодно и надёжность выше, смерть по ошибке форка не влияет на работу демона.

KRoN73 ★★★★★ ()
Ответ на: комментарий от KRoN73

Вообще, в теме не прозвучала версия PHP.

разве? с первого поста, там и метка есть...

Впрочем, я и на старых версиях PHP делал демонов, активно работающих с памятью

там постоянно какие-то косяки с массивами. Их правят, но они опять вылезают. Причём тип array в php всего один, но использовать его можно как угодно, фактически, это несколько базовых структур данных...

Когда он закончит работу, память за ним будет подчищена уже системой. Заодно и надёжность выше, смерть по ошибке форка не влияет на работу демона.

согласен, это удачный подход.

drBatty ★★ ()
Ответ на: комментарий от drBatty

не прозвучала версия PHP

с первого поста, там и метка есть

«Метки: php, память»

?

там постоянно какие-то косяки с массивами. Их правят, но они опять вылезают

Ну, я пока не наступал.

KRoN73 ★★★★★ ()
Ответ на: комментарий от KRoN73

да, именно версия не прозвучала...

Ну, я пока не наступал.

у меня были проблемы с быстродействием на некоторых старых версиях. Прошло само с обновлением. Не копал особо, но в некоторых случаях операции с массивами занимают слишком много времени (раз в 10 больше, чем обычно).

drBatty ★★ ()
Ответ на: комментарий от KRoN73

К вопросу о версии:

ep9315:/etc/php5/apache2# php -v                                                
PHP 5.3.3-7+squeeze8 with Suhosin-Patch (cli) (built: Feb 10 2012 17:00:00)     
Copyright (c) 1997-2009 The PHP Group                                           
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies

Демон для моей задачи - слишком круто. Тут реальная проблема в быстродействии железа. На рабочем компе все летает, а как заливаю на одноплатник - начинает тормозить, что вполне логично...

solovey ★★ ()
Ответ на: комментарий от solovey

Простите что вмешиваюсь...

... но уважаемый коллега drBatty предложил поколупаться в данной задаче.

Собственно, со своими выводами я малость припоздал, по-моему, Вы и сами поняли причину тормозов — железо. ;)

Однако, прочитав Ваш последний постинг, я малость призадумался. Короче, что у меня получилось (это не более чем гипотеза, прошу не пинать сильно). Есть некий отладочный скрипт для отладки и оценки типа (файл memdbg.php, его где-то коллега скопипастил, так и прижился.):

<?php
class MemoryUsageInformation {
private $real_usage;
private $statistics = array();

    public function __construct($real_usage = false) {
      $this->real_usage = $real_usage;
    }
 
    public function getCurrentMemoryUsage($with_style = true) {
      $mem = memory_get_usage($this->real_usage);
      return ($with_style) ? $this->byteFormat($mem) : $mem;
    }
 
    public function getPeakMemoryUsage($with_style = true) {
      $mem = memory_get_peak_usage($this->real_usage);
      return ($with_style) ? $this->byteFormat($mem) : $mem;
    }
 
    public function setMemoryUsage($info = '') {
      $this->statistics[] = array('time' => time(), 
                                  'info' => $info, 
                                  'memory_usage' => $this->getCurrentMemoryUsage());
    }
 
    public function printMemoryUsageInformation() {
      foreach ($this->statistics as $satistic) {
        echo  "Time: " . $satistic['time'] . 
              " | Memory Usage: " . $satistic['memory_usage'] . 
              " | Info: " . $satistic['info'];
        echo "\n";
      }
      echo "\n\n";
      echo "Peak of memory usage: " . $this->getPeakMemoryUsage();
      echo "\n\n";
    }

    public function setStart($info = 'Initial Memory Usage') {
      $this->setMemoryUsage($info);
    }

    public function setEnd($info = 'Memory Usage at the End') {
      $this->setMemoryUsage($info);
    }

    private function byteFormat($bytes, $unit = "", $decimals = 2) {
    $units = array('B' => 0, 'KB' => 1, 'MB' => 2, 'GB' => 3, 'TB' => 4, 
    'PB' => 5, 'EB' => 6, 'ZB' => 7, 'YB' => 8);
    $value = 0;
    if ($bytes > 0) {
    if (!array_key_exists($unit, $units)) {
    $pow = floor(log($bytes)/log(1024));
    $unit = array_search($pow, $units);
    }
    $value = ($bytes/pow(1024,floor($units[$unit])));
    }
    if (!is_numeric($decimals) || $decimals < 0) {
    $decimals = 2;
    }
    return sprintf('%.' . $decimals . 'f '.$unit, $value);
    }
  }
?>

Тестовый скрипт (test.php):

<?php
require('memdbg.php');

$m = new MemoryUsageInformation(true);
$m->setStart();
$a = array();
$m->setMemoryUsage("до цикла");
for($i = 0; $i < 100000; $i++) {
    $a[$i] = uniqid();
  }
$m->setMemoryUsage("после цикла");
unset($a);
$m->setMemoryUsage("после unset()");
$m->setEnd();
$m->printMemoryUsageInformation();

Вывод получился (после php test.php):

Time: 1339275216 | Memory Usage: 256.00 KB | Info: Initial Memory Usage
Time: 1339275216 | Memory Usage: 256.00 KB | Info: Before Loop
Time: 1339275222 | Memory Usage: 16.25 MB | Info: After Loop
Time: 1339275222 | Memory Usage: 768.00 KB | Info: After Unset
Time: 1339275222 | Memory Usage: 768.00 KB | Info: Memory Usage at the End

Peak of memory usage: 16.25 MB

Но потом я прочёл:

Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies

И вспомнил что в php есть такая вещь как garbage collector. Включил его (просто добавляем в test.php следующие 4 строки (сразу после require() ):

gc_enable(); 
var_dump(gc_enabled());
var_dump(gc_collect_cycles()); 
gc_disable(); 

Но где-то в документации вот что написано:

Activates the circular reference collector, setting zend.enable_gc to 1.

А у Вас как-раз Zend есть. Механизм GC в данном случае является синхронным и не факт что он просто успевает до конца выполнить свою работу. Вот и получается что вроде как память уже освобождена, но на деле php не успел её полностью обработать и считает что она занята. В результате, в сумме, и набегает довольно много.

Подчеркну ещё раз — это не более чем просто рабочая гипотеза. Для более полного разбора ситуации надо трассировать исполнение тестового кода в php и смотреть на выделение/освобождение памяти. Сейчас нет на это время, к сожалению.

mr_noone ()
Ответ на: Простите что вмешиваюсь... от mr_noone

В качестве вывода.

1. Попробовать скрипт с включённым и отключённым gc.

2. Попробовать дать пыху больший приоритет (nice/renice) и п. 1.

3. В случае, если сейчас используется Apache, попробовать использовать однопоточный lighttpd. Он у меня работает без проблем на Asus WL-500gPv2. Ну и опять пп. 1 и 2.

4. В дальнейшем подумать о написании CGI/FastCGI-приложений на С. Коду будет проще жить на машине.

Как-то так, в общем.

mr_noone ()
Ответ на: В качестве вывода. от mr_noone

Блин. Забыл.

Если интересно глубже, то strace php test.php, можно правда, grep'ом вывод обработать, но блин... Это дофига всего. У меня получилось (на рабочей машине):

munmap(0xb594d000, 266240)              = 0
munmap(0xb58cb000, 266240)              = 0
munmap(0xb590c000, 266240)              = 0
munmap(0xb5849000, 266240)              = 0
munmap(0xb5808000, 266240)              = 0
munmap(0xb57c7000, 266240)              = 0
munmap(0xb5786000, 266240)              = 0
munmap(0xb5745000, 266240)              = 0
munmap(0xb588a000, 266240)              = 0
munmap(0xb56c3000, 266240)              = 0
munmap(0xb5682000, 266240)              = 0
munmap(0xb5641000, 266240)              = 0
munmap(0xb5600000, 266240)              = 0
munmap(0xb55bf000, 266240)              = 0
munmap(0xb557e000, 266240)              = 0
munmap(0xb553d000, 266240)              = 0
munmap(0xb54fc000, 266240)              = 0
munmap(0xb54bb000, 266240)              = 0
munmap(0xb5704000, 266240)              = 0
munmap(0xb547a000, 266240)              = 0
munmap(0xb53b8000, 266240)              = 0
munmap(0xb5377000, 266240)              = 0
munmap(0xb5336000, 266240)              = 0
munmap(0xb52f5000, 266240)              = 0
munmap(0xb52b4000, 266240)              = 0
munmap(0xb5273000, 266240)              = 0
munmap(0xb5232000, 266240)              = 0
munmap(0xb51f1000, 266240)              = 0
munmap(0xb51b0000, 266240)              = 0
munmap(0xb516f000, 266240)              = 0
munmap(0xb512e000, 266240)              = 0
munmap(0xb50ed000, 266240)              = 0
munmap(0xb50ac000, 266240)              = 0
munmap(0xb506b000, 266240)              = 0
munmap(0xb502a000, 266240)              = 0
munmap(0xb4fe9000, 266240)              = 0
munmap(0xb4fa8000, 266240)              = 0
munmap(0xb4f67000, 266240)              = 0
munmap(0xb4f26000, 266240)              = 0
munmap(0xb5439000, 266240)              = 0
munmap(0xb4e24000, 266240)              = 0
munmap(0xb4de3000, 266240)              = 0
munmap(0xb4da2000, 266240)              = 0
munmap(0xb4d61000, 266240)              = 0
munmap(0xb4d20000, 266240)              = 0
munmap(0xb4cdf000, 266240)              = 0
munmap(0xb4c9e000, 266240)              = 0
munmap(0xb4c5d000, 266240)              = 0
munmap(0xb4c1c000, 266240)              = 0
munmap(0xb4bdb000, 266240)              = 0
munmap(0xb4b9a000, 266240)              = 0
munmap(0xb4b59000, 266240)              = 0
munmap(0xb4b18000, 266240)              = 0
munmap(0xb4ad7000, 266240)              = 0
munmap(0xb4a96000, 266240)              = 0
munmap(0xb4a55000, 266240)              = 0
munmap(0xb4a14000, 266240)              = 0
munmap(0xb49d3000, 266240)              = 0
munmap(0xb4992000, 266240)              = 0
munmap(0xb4e65000, 790528)              = 0
т.е., вроде бы всё относительно правильно...

mr_noone ()

Проще закопать PHP. Массивы там например ужасно память расходуют. Короче поделие для домашних говностраничек, не более того.

anonymous ()
Ответ на: Блин. Забыл. от mr_noone

Спасибо за очень развернутый ответ!

Приятно поражен тем, что Вы взялись исследовать эту проблему. Скорее всего, Ваша гипотеза верна. Когда будет время, попробую включить/отключить gc и поиграть с приоритетом.

Сишную либу если и буду делать, то уже в следующей версии продукта.

Дело в том, что сейчас я уже полностью переписал тот кусок кода и увеличил его быстродействие примерно в 2 раза. Теперь уже нет возможности вернуться к тому коду - сроки поджимают.

Но, в любом случае, еще раз спасибо за столь подробный ответ!

solovey ★★ ()
Ответ на: комментарий от solovey

Незачто.

Собственно говоря, это одна из тех задач, которые я с «завидной» регулярностью вынужден решать. Работа у меня такая — искать нестыковки и проводить... вскрытия.

Сишную либу если и буду делать, то уже в следующей версии продукта.

Такая либа есть у меня и самопальная, если что — скажите, готов поделиться. Кроме моего поделия просто оставляю здесь FYI (или может кому сгодится) небольшой списочек для ознакомления с небольшими характеристиками (при создании своей либы я с этого начинал) :

  • Самая известная, от автора gd — cgic
  • Менее известная, но имеет функционал по работе с пользовательскими сессиями — libcgi

Из двух выше приведённых, если бы я не имел своего варианта, то пользовался бы второй библиотекой. Если у Вас предполагается работа с БД, то ещё рекомендую посмотреть на FastCGI.

По поводу сервера подо всё это, то из опыта могу сказать что например функционала сервера для газонокосилок и кофеварок как правило, вполне довольно. Размер в стандартном окружении что-то в районе 140K, на однокристалках, с uclibc — что-то в районе от 90K (при условии статической линковки). Ну, что там с одноплаткой будет — зависит от того что и как Вы там используете. Если ту же uclibc вместо glibc, то само собой всё будет по размерам и ресурсам меньше.

Успехов.

mr_noone ()
Ответ на: Незачто. от mr_noone

Спасибо!

Либы и сервер обязательно изучу!

solovey ★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.