Проблема с чтением файлов размером более 1 ГБ с помощью XMLReader

Существует ли максимальный размер файла, который может обрабатывать XMLReader?

Я пытаюсь обработать XML-канал размером около 3 ГБ. Ошибок PHP, безусловно, нет, так как скрипт работает нормально и успешно загружается в базу данных после запуска.

Скрипт также отлично работает с меньшими тестовыми потоками — 1 ГБ и меньше. Однако при обработке больших каналов сценарий прекращает чтение XML-файла примерно через 1 ГБ и продолжает выполнять остальную часть сценария.

Кто-нибудь сталкивался с подобной проблемой? и если да, то как вы обошли это?

Заранее спасибо.


person A boy named Su    schedule 06.08.2010    source источник
comment
Вы уверены, что ошибок PHP не возникает? Что именно (насколько вы можете судить) является определяющим фактором между работой и не работой? Как выглядит скрипт, что еще он делает, кроме перебора XML?   -  person salathe    schedule 06.08.2010
comment
В псевдокоде скрипт выглядел бы примерно так: $this-›downloadFeed(); try{ $this-›writeXMLFeedToCSV(); }catch(e){ //обработка исключения } $this-›uploadCSVToDatabaseTable(); Если сценарий завершился ошибкой из-за ошибки PHP, он не будет загружен в базу данных. В настоящее время это так. XML также правильно сформирован, как и в случае разбивки скрипта, как предположил ircmaxell, он работает нормально. Однако процесс утомительный, и я надеялся найти решение. Извините, из-за характера информации я не имею права делиться сценарием.   -  person A boy named Su    schedule 06.08.2010
comment
Какую а) операционную систему б) файловую систему в) версию php г) сборку php вы используете для тестирования?   -  person VolkerK    schedule 06.08.2010


Ответы (6)


Недавно у меня была такая же проблема, и я решил поделиться своим опытом.

Похоже, проблема заключается в том, как был скомпилирован PHP, был ли он скомпилирован с поддержкой 64-битных размеров/смещений файлов или только с 32-битными.

С 32-битной вы можете адресовать только 4 ГБ данных. Вы можете найти немного запутанное, но хорошее объяснение здесь: http://blog.mayflower.de/archives/131-Handling-large-files-without-PHP.html

Мне пришлось разделить мои файлы с помощью утилиты Perl xml_split, которую вы можете найти здесь: http://search.cpan.org/~mirod/XML-Twig/tools/xml_split/xml_split

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

Мне нужно было сделать это только один раз, и это соответствовало моим потребностям, но я бы не рекомендовал его повторное использование. После разделения я использовал XMLReader для файлов меньшего размера размером около 1GB.

person gazda    schedule 20.01.2012

Разделение файла определенно поможет. Другие вещи, чтобы попробовать...

  1. настроить переменную memory_limit в php.ini. http://php.net/manual/en/ini.core.php
  2. перепишите свой синтаксический анализатор с помощью SAX -- http://php.net/manual/en/book.xml.php . Это потоковый синтаксический анализатор, которому не нужно анализировать все дерево. Гораздо более эффективно использует память, но немного сложнее в программировании.

В зависимости от вашей ОС также может быть ограничение в 2 ГБ на объем ОЗУ, который вы можете выделить. Очень возможно, если вы работаете в 32-битной ОС.

person Vineel Shah    schedule 06.08.2010
comment
Предполагается, что интерфейс XMLReader обрабатывает большие документы последовательно, как синтаксический анализатор SAX, т. е. он (обязательно) не загружает весь документ в память. - person VolkerK; 06.08.2010
comment
Спасибо за это. Пришлось уже настраивать внутреннюю память. VolkerK тоже прав. XMLReader читает так же, как парсер SAX. Я попробую это с SAX, если ничего не получится, но лучше не переписывать сценарий. - person A boy named Su; 06.08.2010

Следует отметить, что PHP в целом имеет максимальный размер файла. PHP не допускает целые числа без знака или длинные целые числа, что означает, что вы ограничены 2 ^ 31 (или 2 ^ 63 для 64-битных систем) для целых чисел. Это важно, потому что PHP использует целое число для указателя файла (ваша позиция в файле при чтении), что означает, что он не может обрабатывать файл размером более 2^31 байт.

Однако это должно быть больше 1 гигабайта. У меня возникли проблемы с двумя гигабайтами (как и ожидалось, поскольку 2 ^ 31 составляет примерно 2 миллиарда).

person Soup d'Campbells    schedule 11.08.2010

Я столкнулся с подобной проблемой при разборе больших документов. В итоге я разбил ленту на более мелкие фрагменты с помощью функций файловой системы, а затем проанализировал эти более мелкие фрагменты... Так что, если у вас есть набор тегов <record>, которые вы анализируете, анализируйте их с помощью строковых функций в виде потока, а когда вы получаете полную запись в буфере, анализируете ее с помощью функций xml... Это отстой, но работает довольно хорошо (и очень эффективно использует память, так как у вас есть не более 1 записи в памяти в любой момент).. .

person ircmaxell    schedule 06.08.2010
comment
Спасибо, да, я тоже так поступил. Но, как вы упомянули, это отстой :o) Вы случайно не знаете, существует ли максимальный размер файла, который может прочитать xml-ридер? - person A boy named Su; 06.08.2010
comment
Еще раз спасибо за ваше предложение, я обнаружил источник ошибки и решение, которое до сих пор работало для меня, и подумал, что вы сможете его реализовать. Оказывается, в ленте была вертикальная табуляция (^K или символ 11), которая не является недопустимым символом, но недопустима для типа документа, который я использовал. Перед обработкой фида я пропустил фид через поиск и замену sed и с тех пор смог анализировать поля размером более 2 ГБ. Спасибо всем остальным за ваши предложения. - person A boy named Su; 19.08.2010

Вы получаете какие-либо ошибки с

libxml_use_internal_errors(true);
libxml_clear_errors();

// your parser stuff here....    
$r = new XMLReader(...);
// ....


foreach( libxml_get_errors() as $err ) {
   printf(". %d %s\n", $err->code, $err->message);
}

когда парсер останавливается преждевременно?

person VolkerK    schedule 06.08.2010
comment
Нет, не бери. Я собираю автономную копию сценария, которая может пролить больше света на проблему, но я совершенно уверен, что это не проблема с XML или самим сценарием PHP. Пока файл меньше 1 ГБ, он работает так, как должен, без проблем. даже когда он больше, он работает нормально, просто не читает весь xml. Спасибо за предложение. - person A boy named Su; 06.08.2010
comment
но я совершенно уверен, что это не проблема с XML или самим PHP-скриптом. - Только для того, чтобы убедиться: функция libxml_get_errors() не означает, что что-то не так со сценарием или XML-документом. Я думал, что libxml может жаловаться на неудачный поиск файла или текстовый узел, размер которого превышает разрешенный максимум (который по умолчанию составляет 10 МБ) или что-то в этом роде. Если вы столкнулись с проблемой без возврата libxml_get_errors() ошибки, эта идея мертва :( - person VolkerK; 06.08.2010
comment
:о) Я знаю, что ты это имел в виду. Я не чувствителен - я не защищался. Извините, если я столкнулся с таким. - person A boy named Su; 06.08.2010

При использовании WindowsXP, NTFS в качестве файловой системы и php 5.3.2 с этим тестовым скриптом проблем не возникло.

<?php
define('SOURCEPATH', 'd:/test.xml');

if ( 0 ) {
  build();
}
else {
  echo 'filesize: ', number_format(filesize(SOURCEPATH)), "\n";
  timing('read');
}

function timing($fn) {
  $start = new DateTime();
  echo 'start: ', $start->format('Y-m-d H:i:s'), "\n";
  $fn();
  $end = new DateTime();
  echo 'end: ', $start->format('Y-m-d H:i:s'), "\n";
  echo 'diff: ', $end->diff($start)->format('%I:%S'), "\n";
}

function read() {
  $cnt = 0;
  $r = new XMLReader;
  $r->open(SOURCEPATH);
  while( $r->read() ) {
    if ( XMLReader::ELEMENT === $r->nodeType ) {
      if ( 0===++$cnt%500000 ) {
        echo '.';
      }
    }
  }
  echo "\n#elements: ", $cnt, "\n";
}

function build() {
  $fp = fopen(SOURCEPATH, 'wb');

  $s = '<catalogue>';
  //for($i = 0; $i < 500000; $i++) {
  for($i = 0; $i < 60000000; $i++) {
    $s .= sprintf('<item>%010d</item>', $i);
    if ( 0===$i%100000 ) {
      fwrite($fp, $s);
      $s = '';
      echo $i/100000, ' ';
    }
  }

  $s .= '</catalogue>';
  fwrite($fp, $s);
  flush($fp);
  fclose($fp);
}

выход:

filesize: 1,380,000,023
start: 2010-08-07 09:43:31
........................................................................................................................
#elements: 60000001
end: 2010-08-07 09:43:31
diff: 07:31

(как видите, я напортачил с выводом конечного времени, но я не хочу запускать этот скрипт еще 7+ минут ;-))

Это также работает в вашей системе?


В качестве примечания: соответствующее тестовое приложение C# заняло всего 41 секунду вместо 7,5 минут. И мой медленный жесткий диск мог быть ограничивающим фактором в этом случае.

filesize: 1.380.000.023
start: 2010-08-07 09:55:24
........................................................................................................................

#elements: 60000001

end: 2010-08-07 09:56:05
diff: 00:41

и источник:

using System;
using System.IO;
using System.Xml;

namespace ConsoleApplication1
{
  class SOTest
  {
    delegate void Foo();
    const string sourcepath = @"d:\test.xml";
    static void timing(Foo bar)
    {
      DateTime dtStart = DateTime.Now;
      System.Console.WriteLine("start: " + dtStart.ToString("yyyy-MM-dd HH:mm:ss"));
      bar();
      DateTime dtEnd = DateTime.Now;
      System.Console.WriteLine("end: " + dtEnd.ToString("yyyy-MM-dd HH:mm:ss"));
      TimeSpan s = dtEnd.Subtract(dtStart);
      System.Console.WriteLine("diff: {0:00}:{1:00}", s.Minutes, s.Seconds);
    }

    static void readTest()
    {
      XmlTextReader reader = new XmlTextReader(sourcepath);
      int cnt = 0;
      while (reader.Read())
      {
        if (XmlNodeType.Element == reader.NodeType)
        {
          if (0 == ++cnt % 500000)
          {
            System.Console.Write('.');
          }
        }
      }
      System.Console.WriteLine("\n#elements: " + cnt + "\n");
    }

    static void Main()
    {
      FileInfo f = new FileInfo(sourcepath);
      System.Console.WriteLine("filesize: {0:N0}", f.Length);
      timing(readTest);
      return;
    }
  }
}
person VolkerK    schedule 07.08.2010