Проблема чтения файлов размером более 1 ГБ с XMLReader - PullRequest
4 голосов
/ 06 августа 2010

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

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

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

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

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

Ответы [ 6 ]

2 голосов
/ 20 января 2012

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

Кажется, проблема в том, как был скомпилирован 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.

1 голос
/ 12 августа 2010

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

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

1 голос
/ 06 августа 2010

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

  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-битной ОС.

0 голосов
/ 07 августа 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;
    }
  }
}
0 голосов
/ 06 августа 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);
}

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

0 голосов
/ 06 августа 2010

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

...