Странное потребление памяти

babahalki

Постоялец
Регистрация
6 Май 2016
Сообщения
247
Реакции
107
Всем привет.
Столкнулся с нехваткой 512мб для выполнения скрипта. Расстановка get_memory_usage() показала, что памяти хватает на выполнение главного цикла, но на втором цикле память не освобождалась, в результате fatal error.

Сломал себе всю голову. Сначала делал обнуление всех переменных после их использования. через $var = ''; $var = null;
Потом делал unset($var);
Потом сразу и то и другое. Ничего не помогало.

Дальше сделал следующее, поделил весь скрипт на 3 функции. Насколько я правильно понял, после выполнения функции все ее локальные переменные и их данные выгружаются из памяти.

Получилось вот такие 3 функции.

Первая функция делает многомерный массив с набором параметров id, name, value. Она выполняется дважды с разными параметрами, чтобы получить 2 массива.
Код:
function feat_opt ($f0_ids, $f0_object, $c) {
       $okay = new Okay();
       $o0_object = $okay->features->get_categories_options(array('category_id'=>$c->children, 'feature_id'=>$f0_ids));
       dtimer::reset();
       foreach($o0_object as $o0) {
         foreach($f0_object as $f0) {
           if ($f0->id == $o0->feature_id) {
           //$features_array[] = array($f0->id=>array($o0->translit));
           $feat_opt[] = array('name'=>$f0->name, 'url'=>$f0->url, 'id'=>$f0->id, 'value'=>$o0->value, 'translit'=>$o0->translit);
           }
         }
       }
       return $feat_opt;
}

Вторая функция перемножает 2 массива между собой и выдает массив. id, id2, name, name2, но так чтобы если есть пара id=>1 id2=>2, пара id=>2 id2=>1 не создавалась.
Код:
function feat_mix ($feat0, $feat1) {
       $okay = new Okay();
       $keys = array();
       $k1 = 0;
     foreach($feat0 as $opt0) {
       $k1++;
       $k2 = 0;
       foreach($feat1 as $opt1) {
         $k2++;
         if ($opt0['id'] != $opt1['id']) {
           $key = $k1 * $k2 + $k1 + $k2;

           if (!array_key_exists($key,$keys)) {
           $keys[$key] = 1;
           $feat_opt_mix[] = array(
           'key' => $key,
           'name'=>$opt0['name'],
           'url'=>$opt0['url'],
           'id'=>$opt0['id'],
           'value'=>$opt0['value'],
           'translit'=>$opt0['translit'],
           'name2'=>$opt1['name'],
           'url2'=>$opt1['url'],
           'id2'=>$opt1['id'],
           'value2'=>$opt1['value'],
           'translit2'=>$opt1['translit']
           );
           }
         }
       }
     }
       return $feat_opt_mix;
}


3 функция пишет массив в xml файл.
Код:
function write_file ($feat_mix) {
     foreach($feat_mix as $opt) {
       $f0_name = $opt['name'];
       $f0_url = $opt['url'];
       $o0_value = $opt['value'];
       $o0_translit = $opt['translit'];
       $f1_name = $opt['name2'];
       $f1_url = $opt['url2'];
       $o1_value = $opt['value2'];
       $o1_translit = $opt['translit2'];
       $url = '/catalog/'.esc($c->url).'/'.esc($f0_url).'-'.esc($o0_translit).'/'.esc($f1_url).'-'.esc($o1_translit);
       $last_modify =  $c->last_modify;
       $last_modify = substr($last_modify, 0, 10);
       file_put_contents("temp.xml", "<url>"."\n", FILE_APPEND);
       file_put_contents("temp.xml", "<loc>$url</loc>"."\n", FILE_APPEND);
       file_put_contents("temp.xml", "<lastmod>$last_modify</lastmod>"."\n", FILE_APPEND);
       file_put_contents("temp.xml", "</url>"."\n", FILE_APPEND);
       }
     return null;
     }

в самой программе следующее.

Код:
print "before write_file memory usage: ".memory_get_usage(true)." bytes\r\n";

write_file(feat_mix(feat_opt($f0_ids, $f0_object, $c), feat_opt($f1_ids, $f1_object, $c)));
     }
print "after write_file memory usage: ".memory_get_usage(true)." bytes\r\n";

Потребление памяти не изменилось, до функции 80мб, после функции 170мб, дальше fatal error.

Стоило мне поменять цикл с foreach на while, и все заработало, поменял только в третьей функции, которая писала большой массив в файл.

Код:
function write_file ($feat_mix) {
     while(count($feat_mix) > $i) {
       $f0_name = $feat_mix[$i]['name'];
       $f0_url = $feat_mix[$i]['url'];
       $o0_value = $feat_mix[$i]['value'];
       $o0_translit = $feat_mix[$i]['translit'];
       $f1_name = $feat_mix[$i]['name2'];
       $f1_url = $feat_mix[$i]['url2'];
       $o1_value = $feat_mix[$i]['value2'];
       $o1_translit = $feat_mix[$i]['translit2'];
       $url = '/catalog/'.esc($c->url).'/'.esc($f0_url).'-'.esc($o0_translit).'/'.esc($f1_url).'-'.esc($o1_translit);
       $last_modify =  $c->last_modify;
       $last_modify = substr($last_modify, 0, 10);
       file_put_contents("temp.xml", "<url>"."\n", FILE_APPEND);
       file_put_contents("temp.xml", "<loc>$url</loc>"."\n", FILE_APPEND);
       file_put_contents("temp.xml", "<lastmod>$last_modify</lastmod>"."\n", FILE_APPEND);
       file_put_contents("temp.xml", "</url>"."\n", FILE_APPEND);
       $i++;
       }
     return null;
     }


Вопрос заключается в следующем. Чем отличаются циклы foreach и while с точки зрения потребления памяти?

Почему локальные переменные и их данные после выполнения функции не освобождали память?
 
Чем отличаются циклы foreach и while с точки зрения потребления памяти
Они отличаются в способе работы.
While до выполнения условия, foreach — пока не переберёт весь массив.
Возможно, в случае с while просто не весь массив перебирается, потому потребление памяти меньше.

В функции мне непонятно:
1. Почему 4 раза надо открывать файл на запись? Почему нельзя сделать это 1 раз?
2. Зачем присваивать переменные внутри цикла, зачем вообще их создавать, если они используются 1 раз внутри функции?
3. Нет проверки на ошибки. Уверены, что входящий массив обладает тему ключами, что используете? Нет ли там вложенного массива среди
$feat_mix?

Вообще, после перебора массива рекомендуют уничтожать временные переменные Для просмотра ссылки Войди или Зарегистрируйся
Но как бы у Вас это не должно отражаться, т.к. они будут уничтожаться автоматом — вы же не по ссылке передаёте, да и перебор массива внутри функции.

Ещё причина может быть в том, что при манипуляции с массивом при переборе часто создаётся копия массива. Я не помню точно, но, кажется, в PHP 7 некоторые стремные куски кода работы с массивами были переписаны.
 
Последнее редактирование:
Они отличаются в способе работы.
While до выполнения условия, foreach — пока не переберёт весь массив.
Возможно, в случае с while просто не весь массив перебирается, потому потребление памяти меньше.

В функции мне непонятно:
1. Почему 4 раза надо открывать файл на запись? Почему нельзя сделать это 1 раз?
2. Зачем присваивать переменные внутри цикла, зачем вообще их создавать, если они используются 1 раз внутри функции?
3. Нет проверки на ошибки. Уверены, что входящий массив обладает тему ключами, что используете? Нет ли там вложенного массива среди
$feat_mix?

Вообще, после перебора массива рекомендуют уничтожать временные переменные Для просмотра ссылки Войди или Зарегистрируйся
Но как бы у Вас это не должно отражаться, т.к. они будут уничтожаться автоматом — вы же не по ссылке передаёте, да и перебор массива внутри функции.

Ещё причина может быть в том, что при манипуляции с массивом при переборе часто создаётся копия массива. Я не помню точно, но, кажется, в PHP 7 некоторые стремные куски кода работы с массивами были переписаны.

Насчёт записи файла, пожалуй, быстрее будет 1 раз открыть и 1 раз закрыть. Спасибо.

Переменные в функции остались с тех пор, когда этот кусок был частью самого скрипта.

С уничтожением переменных - бесполезно. Я их и unset и обнулял, и unset по элементам массива. Сработало только если отказаться от foreach. While и for работает нормально - отработала функция и память освободилась.
Массив feat_mix, как видно из кода является индексным двумерным. Внутри него набор массивов ассоциативных по 11 элементов в каждом.

Как-то особенно работает foreach, видимо там не только 1 копия создаётся, которая меняется в цикле. Как бы отследить что именно в памяти остаётся?
 
Как-то особенно работает foreach, видимо там не только 1 копия создаётся, которая меняется в цикле. Как бы отследить что именно в памяти остаётся?
Мне кажется, надо сначала убедиться, что while и for действительно перебирают весь массив.
А где создается объект $c?
Я их и unset и обнулял, и unset по элементам массива. Сработало только если отказаться от foreach.
Значит как-то не так «обнуляли».

Вообще, всю функцию write_file можно было записать в 1 строчку...
 
Дамп вот этих объектов покажите
PHP:
$o0_object = $okay->features->get_categories_options(array('category_id'=>$c->children, 'feature_id'=>$f0_ids));

2е, а как вы определять собрались, где затык?
Правильный тест:
PHP:
print "start memory usage: ".memory_get_usage(true)." bytes\r\n";

$optOne = feat_opt($f0_ids, $f0_object, $c);

print "feat_opt 1 memory usage: ".memory_get_usage(true)." bytes\r\n";

$optTwo = feat_opt($f1_ids, $f1_object, $c);

print "feat_opt 2 memory usage: ".memory_get_usage(true)." bytes\r\n";

$mix = feat_mix($optOne, $optTwo);

print "feat_mix memory usage: ".memory_get_usage(true)." bytes\r\n";

write_file($mix);

print "write_file memory usage: ".memory_get_usage(true)." bytes\r\n";
 
Затык происходит на write_file($mix);
Память не освобождается почти после этой функции, только мегабайт 20-30, и на втором цикле уже die.

Вот print_r o0_object
Для просмотра ссылки Войди или Зарегистрируйся

А где создается объект $c?
Объект $c создается в цикле foreach.
Код:
[QUOTE="Den1xxx, post: 2640255, member: 354212"]Мне кажется, надо сначала убедиться, что while и for действительно перебирают весь массив.[/QUOTE]
Так в условиях на for и стоит счетчик. 

Это кусок кода целиком, где память не течет и все ок. 
[code]
//*
// функции для объединения массивов со значениями и массива с названиями опций
function feat_opt($f0_ids, $f0_object, $c, $b) {
       $okay = new Okay();
       $o0_object = $okay->features->get_categories_options(array('category_id'=>$c->children, 'brand_id'=>$b->id, 'feature_id'=>$f0_ids));
       file_put_contents('o0_object'.$c->url.'txt', print_r($o0_object, true));
       dtimer::reset();
       for($i = 0, $count = count($o0_object); $count > $i; $i++) {
         for($i2 = 0, $count2 = count($f0_object); $count2 > $i2; $i2++) {
           if ($f0_object[$i2]->id == $o0_object[$i]->feature_id) {
           //$features_array[] = array($f0_object[$i2]->id=>array($o0_object[$i]->translit));
           $feat_opt[] = array(
           'name'=>$f0_object[$i2]->name,
           'url'=>$f0_object[$i2]->url,
           'id'=>$f0_object[$i2]->id,
           'value'=>$o0_object[$i]->value,
           'translit'=>$o0_object[$i]->translit);
           }
         }
       }
       return $feat_opt;
}

// функция для перемножения массивов
function feat_mix($feat0, $feat1) {
       $okay = new Okay();
       $keys = array();
       $k1 = 0;
     for($i =0, $count = count($feat0); $count > $i; $i++) {
       $k1++;
       $k2 = 0;
       for($i2 = 0, $count2 = count($feat1); $count2 > $i2; $i2++) {
         $k2++;
         if ($feat0[$i]['id'] != $feat1[$i2]['id']) {
           $key = $k1 * $k2 + $k1 + $k2;

           if (!array_key_exists($key,$keys)) {
           $keys[$key] = 1;
           $feat_opt_mix[] = array(
           'key' => $key,
           'name'=>$feat0[$i]['name'],
           'url'=>$feat0[$i]['url'],
           'id'=>$feat0[$i]['id'],
           'value'=>$feat0[$i]['value'],
           'translit'=>$feat0[$i]['translit'],
           'name2'=>$feat1[$i2]['name'],
           'url2'=>$feat1[$i2]['url'],
           'id2'=>$feat1[$i2]['id'],
           'value2'=>$feat1[$i2]['value'],
           'translit2'=>$feat1[$i2]['translit']
           );
           }
         }
       }
     }
       return $feat_opt_mix;
}
////*/


//*
// Категории + feature + feature


$c = $okay->categories->get_categories();
dtimer::reset();
for (reset($c); $i3 = key($c); next($c)){
if($c[$i3]->visible) {
   //print "before category cycle + f + f memory usage: ".memory_get_usage(true)." bytes\r\n";
   $c_name = $c[$i3]->name;
   $f0_ids = array();
   $f0_object = array();
   $f1_ids = array();
   $f1_object = array();
   $f0 = $okay->features->get_features(array('category_id'=>$c[$i3]->children, 'in_filter'=>1));
   for($i = 0; count($f0) > $i; $i++) {
     // массив $f_ids из небольшого фильтра $filter_minus
     if (!in_array($f0[$i]->id, $filter_minus)) {
       $f0_ids[$f0[$i]->id] = $f0[$i]->id;
       $f0_object[] = $f0[$i];
     }
     // массив $f_ids2 из жесткого фильтра $filter_minus_hard
     if (!in_array($f0[$i]->id, $filter_minus)) {
       $f1_ids[$f0[$i]->id] = $f0[$i]->id;
       $f1_object[] = $f0[$i];
     }
   }

   $feat_mix = feat_mix(feat_opt($f0_ids, $f0_object, $c[$i3], null), feat_opt($f1_ids, $f1_object, $c[$i3], null));
   for($i = 0, $count = count($feat_mix); $count > $i; $i++) {
     $f0_name = $feat_mix[$i]['name'];
     $f0_url = $feat_mix[$i]['url'];
     $o0_value = $feat_mix[$i]['value'];
     $o0_translit = $feat_mix[$i]['translit'];
     $f1_name = $feat_mix[$i]['name2'];
     $f1_url = $feat_mix[$i]['url2'];
     $o1_value = $feat_mix[$i]['value2'];
     $o1_translit = $feat_mix[$i]['translit2'];
     $url = $okay->config->root_url.'/catalog/'.esc($c[$i3]->url).'/'.esc($f0_url).'-'.esc($o0_translit).'/'.esc($f1_url).'-'.esc($o1_translit);
     $last_modify =  $c[$i3]->last_modify;
     $last_modify = substr($last_modify, 0, 10);
     file_put_contents($smap_n_prefix.$sitemap_index.$smap_n_ext, "<url>"."\n", FILE_APPEND);
     file_put_contents($smap_n_prefix.$sitemap_index.$smap_n_ext, "<loc>$url</loc>"."\n", FILE_APPEND);
     file_put_contents($smap_n_prefix.$sitemap_index.$smap_n_ext, "<lastmod>$last_modify</lastmod>"."\n", FILE_APPEND);
     file_put_contents($smap_n_prefix.$sitemap_index.$smap_n_ext, "</url>"."\n", FILE_APPEND);
     if (++$url_index == 50000) {
       file_put_contents($smap_n_prefix.$sitemap_index.$smap_n_ext, '</urlset>'."\n", FILE_APPEND);{}
       $url_index=0;
       $sitemap_index++;
       $sitemaps[] = $sitemap_index;
       if (file_exists($okay->config->root_dir.'/'.$smap_n_prefix.$sitemap_index.$smap_n_ext)) {
         @unlink($okay->config->root_dir.'/'.$smap_n_prefix.$sitemap_index.$smap_n_ext);
       }
       file_put_contents($smap_n_prefix.$sitemap_index.$smap_n_ext, '<?xml version="1.0" encoding="UTF-8"?>'."\n");
       file_put_contents($smap_n_prefix.$sitemap_index.$smap_n_ext, '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'."\n", FILE_APPEND);


     }
   }
   $feat_mix = '';
   //print "after category cycle + f + f memory usage: ".memory_get_usage(true)." bytes\r\n";
}
}
$c = '';

//*/



Значит как-то не так «обнуляли».
Как можно сделать не так?
У меня стояло сразу все по очереди и по отдельности.
была такая последовательность, чтобы наверняка.
в конце цикла:

foreach ($feat_mix as $k=>$f) {
unset($feat_mix[$k]);
}
unset($feat_mix);
$feat_mix = array();

Я реально не изменял кода за исключением способа перебора массива. foreach и память течет, for или while и все ок. В итоге остановился на for, чтобы удобнее было делать нумератор элементов $i++.
Есть ли какой-то способ посмотреть процесс выполнения кода по этапам? get_memory_usage() показывает в каком месте утечка, я написал это. А как бы увидеть чем именно забивается память?
 
Последнее редактирование модератором:
  • Заблокирован
  • #7
foreach создает в определенных условия копию массива, если он итерационный, что приводит к дополнительным расходам памяти
более того, тут то же не без греха, использование в while постоянный пересчет count($feat_mix) - будет замедлять цикл
 
Затык происходит на write_file($mix);
Память не освобождается почти после этой функции, только мегабайт 20-30, и на втором цикле уже die.

...

Я реально не изменял кода за исключением способа перебора массива. foreach и память течет, for или while и все ок. В итоге остановился на for, чтобы удобнее было делать нумератор элементов $i++.
Есть ли какой-то способ посмотреть процесс выполнения кода по этапам? get_memory_usage() показывает в каком месте утечка, я написал это. А как бы увидеть чем именно забивается память?


Массив ни причем
Никогда не используйте file_put_contents() в цикле, тем более во вложенном!! цикле
PHP создает под нее буфер записи

Вместо этого используйте последовательно fopen() , fwrite() и fclose() в конце цикла
Примерно так:

PHP:
function write_file ($feat_mix) {
     foreach($feat_mix as $opt) {
       $file = fopen('temp.xml', 'w')

.....
       fwrite($file, "<url>"."\n");
       fclose($file);
       }
     }
 
Последнее редактирование модератором:
Кроме всего прочего, строка while(count($feat_mix) > $i) будет пересчитывать каждый раз этот самый count при каждой итерации.
Оно вам надо? Правильнее посчитать количество один раз перед циклом, и сравнивать с ним, раз уж оптимизируете.

Естественно, что for/while будут быстрее и легче foreach, если в параметре у вас многомерный массив, а цикл надо сделать по линейному индексу.

И ещё, делая посреди цикла unset($feat_mix[$someindex]), вы можете порушить цикл - количество-то поменяется...
 
Это не ответы на заданные вопросы, но сдается мне, вопросы поставлены не правильные, не ведущие к результату.
Задача похожа на ребус, который для того, чтобы голову сломать себе и окружающим :)
Зачем так писать программы?

1. Вложенные циклы - не есть хорошо... Это зло. Нужно стараться писать без вложенных циклов.
Создание объектов, заполнение массива во вложенном цикле - это зло в квадрате.
Если уж совсем невозможно без вложенных циклов, то уходить от большого количества итераций в этих циклах.

2. Нужно тестировать (человек выше правильно написал) - надо _измерять_, как растет потребляемая память, и как она освобождается при unset, и освобождается ли на самом деле.
(Реализация движка php в данной конкретной версии вполне может быть такой, что unset не сразу освобождает память).
"То, что не измеряешь, тем не управляешь". (c)

Если не следовать правилам написания программ, которые были сформулированы еще в 70х годах, и не измерять параметры процесса - можно такого написать, что 10 мудрецов не разберутся.

3. Можно потратить неделю чтобы найти ответы, а можно не искать ответы, а решить задачу по-другому (не особо думая) в течение двух часов.
Например, сделать два скрипта. Один подготавливает данные и сохраняет их во временные файлы.
Второй скрипт - обрабатывает эти временные файлы. Может не надо всё в памяти стараться держать?

Эх, хороши были времена, когда памяти в компьютере было 48 килобайт. Вот на таких надо учить студентов программировать.
Так человек учится применять приемы, рационально расходующие память.
После этого вот такие задачки-ребусы просто не появляются.
 
Назад
Сверху