DOMDocument / Xpath утечка памяти во время длинного процесса командной строки - любой способ деконструировать этот класс - PullRequest
5 голосов
/ 19 ноября 2011

Я создал приложение для очистки php командной строки, которое использует XPath для анализа HTML - проблема заключается в том, что каждый раз, когда новый экземпляр класса DOMXPath загружается в цикле, я получаю потерю памяти, примерно равную размеру XML загружается. Сценарий запускается и работает, медленно наращивая использование памяти, пока не достигнет предела и не выйдет.

Я пытался форсировать сборку мусора с помощью gc_collect_cycles(), а PHP все еще не возвращает память из старых запросов Xpath. Действительно, определение класса DOMXPath, похоже, даже не включает функцию-деструктор?

Итак, мой вопрос ... есть ли способ принудительно очистить мусор на DOMXPath после того, как я уже извлек необходимые данные? Использование unset на экземпляре класса предсказуемо ничего не делает.

В коде нет ничего особенного, просто стандартные вещи Xpath:

//Loaded outside of loop
$this->dom = new DOMDocument(); 

//Inside Loop
$this->dom->loadHTML($output);  
$xpath = new DOMXPath($this->dom);
$nodes = $xpath->query("//span[@class='ckass']");

//unset($this->dom) and unset($xpath) doesn't seem to have any effect

Как вы можете видеть выше, я сохранил создание нового класса DOMDocument вне цикла, хотя это, похоже, не улучшает производительность. Я даже пытался извлечь экземпляр класса $xpath из цикла и загрузить DOM напрямую в Xpath, используя метод __constructor, потеря памяти такая же.

Ответы [ 2 ]

2 голосов
/ 19 ноября 2011

Увидев этот ответ ей годами без заключения, наконец-то обновление!Теперь я столкнулся с подобной проблемой, и оказалось, что DOMXPath просто утечка памяти, и вы не можете ее контролировать.Я не искал, если об этом сообщалось на bug.php.net до сих пор (это может быть полезно для редактирования позже).

«Рабочие» решения, которые я нашел для этой проблемы, являются просто обходными путями.Основная идея состояла в том, чтобы заменить DOMNodeList Traversable, возвращаемый на DOMXPath::query(), на другой, содержащий те же узлы.

Наиболее подходящее решение - DOMXPathElementsIteratorкоторый позволяет вам запрашивать конкретное выражение xpath, которое у вас есть в вашем вопросе, без утечек памяти:

$nodes = new DOMXPathElementsIterator($this->dom, "//span[@class='ckass']");

foreach ($nodes as $span) {
   ...
}

Этот класс теперь является частью версии разработки Iterator-Garden и $nodes является итератором для всех элементов <span> DOME.

Недостатком этого временного решения является то, что результат xpath ограничен результатом SimpleXMLElement::xpath() (отличается от DOMXPath::query())потому что он используется для предотвращения утечки памяти.

Другая альтернатива - использовать DOMNodeListIterator вместо DOMNodeList, как тот, который возвращается DOMDocument::getElementsByTagname().Однако эти итерации медленные.

Надеюсь, что это пригодится, даже если вопрос был действительно старым.Это помогло мне в подобной ситуации.


Вызов кругов очистки сборки мусора имеет смысл только в том случае, если на объекты больше не ссылаются (не используют).

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

Вместо этого вы можете просто повторно использовать существующий объект DOMXPath, так как вы постоянно используете объект DOMDocument.Попробуйте:

//Loaded outside of loop
$this->dom = new DOMDocument(); 
$xpath = new DOMXPath($this->dom);

//Inside Loop
$this->dom->loadHTML($output);  
$nodes = $xpath->query("//span[@class='ckass']");
1 голос
/ 03 августа 2017

Если вы используете libxml_use_internal_errors(true);, то это является причиной утечки памяти, поскольку список ошибок растет.

Используйте libxml_clear_errors(); или отметьте ответ для подробностей.

...