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

Я создаю приложение для очистки 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, потеря памяти такая же.


person Corelloman    schedule 18.11.2011    source источник
comment
Вы также уничтожаете узлы через unset? Кроме того, если вы просто очищаете и не модифицируете рассматриваемые DOM, я бы вместо этого использовал SimpleXML. Это немного легче, а также поддерживает xpath.   -  person prodigitalson    schedule 19.11.2011
comment
Почему $this->dom? Вам нужно добавить DOMDocument к члену класса?   -  person hakre    schedule 19.11.2011
comment
Я проверю SimpleXML - спасибо! Я использую $this-›dom, так как я объявил $dom вне итерационной функции, поэтому он не создается с каждой итерацией.   -  person Corelloman    schedule 19.11.2011


Ответы (2)


Увидев этот ответ, она годами без вывода, наконец, обновление! Я сейчас столкнулся с похожей проблемой, и оказалось, что 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> DOMElements.

Недостатком этого обходного пути является то, что результат 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']");
person hakre    schedule 18.11.2011
comment
Ах спасибо! Я предполагал, что мне нужно загружать новый DOMXPath каждый раз, когда я хочу загрузить новый контент, это была моя ошибка - огромное спасибо!! - person Corelloman; 19.11.2011
comment
Изменить. Теперь, когда я попробовал это, переменная $xpath, похоже, не принимает содержимое, если я не переобъявляю $xpath = new DOMXPath($this-›dom); после загрузки содержимого в $this-›dom. :( - person Corelloman; 19.11.2011
comment
Ладно, старые объекты, связанные с DOMDocument, остаются в памяти после loadHTML. Не знаю дизайн вашего класса, но, вероятно, вам следует удалить DOM из члена класса перед загрузкой. Удалите DOM, XPath и результаты. Затем каждый раз создавайте новый DOM и XPath внутри цикла. Уже немного поздно, пропустил оператор loadHTML. - person hakre; 19.11.2011
comment
Ха-ха, не беспокойтесь, тем не менее, я ценю ваш вклад, это то, что я должен был попробовать. Вот только я этого не понимаю - я искал везде и все равно не вижу, чтобы вручную избавиться от классов DOM/Xpath. Сброс переменных не освобождает память, есть ли другой способ сделать это, который мне просто не хватает? - person Corelloman; 19.11.2011
comment
Вы должны иметь в виду, что объект XPath внутренне разделяет данные с DOMDocument. Поэтому, если вы создаете один объект из DOMDocument, вам нужно уничтожить оба, чтобы очистить память. - person hakre; 19.11.2011
comment
Но как уничтожить эти объекты? unset не работает, и я не могу найти никаких других методов для уничтожения класса, поскольку в PHP он должен быть автоматическим? - person Corelloman; 19.11.2011
comment
Допустим, вы сделали запрос xpath. Сначала отключите возвращенный DOMNodeList, затем удалите DOMXPath, затем удалите DOMDocument. Затем вызовите цикл сборщика мусора. Это должно сделать это. И да, unset это способ уничтожения в вашем случае, другого в PHP нет. Если вы использовали элементы из DOMDocument где-то еще, отключите их также перед удалением DOMDocument. Все узлы будут содержать ссылку на DOMDocument. Пока вы не отключите их, DOMDocument останется в памяти. - person hakre; 19.11.2011
comment
DOMXPath не обновляется, если DOMDocument используется повторно. Вам нужно каждый раз вызывать новый DOMXPath. У меня такая же проблема с памятью, и ни одно из ваших предложений не работает для меня, используя PHP 5.3.6. Любые дальнейшие мысли? - person ; 05.12.2011
comment
DOMXpath имеет связанный DOMDocument. Если вы измените документ, он по-прежнему будет ссылаться на старые узлы (я предполагаю, что вы уже выполнили запрос xpath). Уничтожьте этот объект xpath, а также результат запроса. Попробуйте инкапсулировать вещи в функцию и/или класс, чтобы ограничить область действия и проблемы с повторным использованием. - person hakre; 14.12.2011

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

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

person Boy    schedule 03.08.2017