Большой PHP для цикла с SimpleXMLElement очень медленный: проблемы с памятью?

В настоящее время у меня есть немного PHP-кода, который в основном извлекает данные из xml-файла и создает простой объект xml, используя $products = new SimpleXMLElement($xmlString); Затем я перебираю этот код с циклом for, в котором я задал детали продукта для каждого продукта в документе XML. Затем он сохраняется в базе данных mySql.

Во время запуска этого скрипта продукты добавляются к уменьшению частоты до тех пор, пока они в конечном итоге не остановятся до достижения максимума. Я пробовал работать сбор мусора в промежутках, безрезультатно. Также, как и отключение различных переменных, которые, похоже, не работают.

Часть кода показана ниже:

 <?php $servername = "localhost"; $username = "database.database"; $password = "demwke"; $database = "databasename"; $conn = new mysqli($servername, $username, $password, $database); $file = "large.xml"; $xmlString = file_get_contents($file); $products = new SimpleXMLElement($xmlString); unset($xmlString, $file); $total = count($products->datafeed[0]); echo 'Starting<br><br>'; for($i=0;$i<$total;$i++){ $id = $products->datafeed->prod[$i]['id']; etc etc $sql = "INSERT INTO products (id, name, uid, cat, prodName, brand, desc, link, imgurl, price, subcat) VALUES ('$id', '$store', '$storeuid', '$category', '$prodName', '$brand', '$prodDesc', '$link', '$image', '$price', '$subCategory')"; } echo '<br>Finished'; ?> 

Все переменные php определяются с помощью аналогичной строки, как и с идентификатором $ id, но удаляются, чтобы упростить чтение.

Любые идеи о том, что я могу сделать / прочитать, чтобы получить это завершение? Время, затраченное на меня, не имеет для меня никакого значения, пока оно заканчивается.

Обновление: никогда не используйте индексы с SimpleXML, если у вас действительно мало объектов. Вместо этого используйте foreach . :

 // Before, with [index]: for ($i=0;$i<$total;$i++) { $id = $products->datafeed->prod[$i]['id']; ... // After, with foreach(): $i = 0; foreach ($products->datafeed->prod as $prod) { $i++; // Remove if you don't actually need $i $id = $prod['id']; ... 

В общем, ...->node[$i] будет обращаться к node[] массива node[] и прочитать все до нужного индекса, так что итерация массива узлов не будет o (N), а o (N 2 ) . Нет обходного пути, потому что нет гарантии, что при доступе к элементу K вы только что получили доступ к элементу K-1 (и так далее рекурсивно). foreach сохраняет указатель и, следовательно, работает в o (N).

По той же причине было бы полезно перебирать массив foreach, даже если вам действительно нужно только несколько известных элементов (если их немного и очень близко к началу массива):

  $a[0] = $products->datafeed->prod[15]['id']; ... $a[35] = $products->datafeed->prod[1293]['id']; // After, with foreach(): $want = [ 15, ... 1293 ]; $i = 0; foreach ($products->datafeed->prod as $prod) { if (!in_array(++$i, $want)) { continue; } $a[] = $prod['id']; } 

Сначала нужно проверить, вызвана ли увеличение задержки MySQLi или обработкой XML. Вы можете удалить (закомментировать) выполнение SQL-запроса, и ничего больше, из цикла, чтобы убедиться, что скорость (теперь она будет намного выше … :-)) остается постоянной или показывает такое же уменьшение.

Я подозреваю, что обработка XML является виновником, здесь:

 for($i=0;$i<$total;$i++){ $id = $products->datafeed->prod[$i]['id']; 

… где вы получаете доступ к индексу, который находится дальше и дальше в SimpleXMLObject . Это может пострадать от проблемы Шлемиеля Художника .

Прямой ответ на ваш вопрос: «Как мне закончить цикл, независимо от времени», это «увеличить лимит памяти и максимальное время выполнения».

Чтобы улучшить производительность, вы можете использовать другой интерфейс в корневом объекте:

 $i = -1; foreach ($products->datafeed->prod as $prod) { $i++; $id = $prod['id']; ... } 

Экспериментируя

Я использую эту небольшую программу для чтения большого XML и перебора его содержимого:

 // Stage 1. Create a large XML. $xmlString = '<?xml version="1.0" encoding="UTF-8" ?>'; $xmlString .= '<content><package>'; for ($i = 0; $i < 100000; $i++) { $xmlString .= "<entry><id>{$i}</id><text>The quick brown fox did what you would expect</text></entry>"; } $xmlString .= '</package></content>'; // Stage 2. Load the XML. $xml = new SimpleXMLElement($xmlString); $tick = microtime(true); for ($i = 0; $i < 100000; $i++) { $id = $xml->package->entry[$i]->id; if (0 === ($id % 5000)) { $t = microtime(true) - $tick; print date("H:i:s") . " id = {$id} at {$t}\n"; $tick = microtime(true); } } 

После генерации XML цикл анализирует его и печатает, сколько требуется, чтобы перезапустить 5000 элементов. Чтобы убедиться, что это действительно дельта времени, дата также печатается. Дельта должна быть примерно равна разнице во времени между отметками времени.

 21:22:35 id = 0 at 2.7894973754883E-5 21:22:35 id = 5000 at 0.38135695457458 21:22:38 id = 10000 at 2.9452259540558 21:22:44 id = 15000 at 5.7002019882202 21:22:52 id = 20000 at 8.0867099761963 21:23:02 id = 25000 at 10.477082967758 21:23:15 id = 30000 at 12.81209897995 21:23:30 id = 35000 at 15.120756149292 

Так вот что происходит: обработка массива XML идет медленнее и медленнее .

Это в основном та же самая программа, использующая foreach:

 // Stage 1. Create a large XML. $xmlString = '<?xml version="1.0" encoding="UTF-8" ?>'; $xmlString .= '<content><package>'; for ($i = 0; $i < 100000; $i++) { $xmlString .= "<entry><id>{$i}</id><text>The quick brown fox did ENTRY {$i}.</text></entry>"; } $xmlString .= '</package></content>'; // Stage 2. Load the XML. $xml = new SimpleXMLElement($xmlString); $i = 0; $tick = microtime(true); foreach ($xml->package->entry as $data) { // $id = $xml->package->entry[$i]->id; $id = $data->id; $i++; if (0 === ($id % 5000)) { $t = microtime(true) - $tick; print date("H:i:s") . " id = {$id} at {$t} ({$data->text})\n"; $tick = microtime(true); } } 

Время кажется постоянным сейчас … Я говорю «кажусь», потому что они, кажется, уменьшились примерно в десять тысяч, и у меня есть некоторые трудности в получении надежных измерений.

(И нет, я понятия не имел. Я, вероятно, никогда не использовал индексы с большими массивами XML).

 21:33:42 id = 0 at 3.0994415283203E-5 (The quick brown fox did ENTRY 0.) 21:33:42 id = 5000 at 0.0065329074859619 (The quick brown fox did ENTRY 5000.) ... 21:33:42 id = 95000 at 0.0065121650695801 (The quick brown fox did ENTRY 95000.) 

Вы можете попробовать увеличить лимит памяти. Если это не вариант, и вам просто нужно сделать это один раз, я бы просто просто разложил его и обработал как 5k значений за раз.

 <?php $servername = "localhost"; $username = "database.database"; $password = "demwke"; $database = "databasename"; $conn = new mysqli($servername, $username, $password, $database); $file = "large.xml"; $xmlString = file_get_contents($file); $products = new SimpleXMLElement($xmlString); unset($xmlString, $file); $total = count($products->datafeed[0]); //get your starting value for this iteration $start = isset($_GET['start'])?(int)$_GET['start']:0; //determine when to stop //process no more than 5k at a time $step = 5000; //where to stop, either after our step (max) or the end $limit = min($start+$step, $total); echo 'Starting<br><br>'; //modified loop so $i starts at our start value and stops at our $limit for this load. for($i=$start;$i<$limit;$i++){ $id = $products->datafeed->prod[$i]['id']; etc etc $sql = "INSERT INTO products (id, name, uid, cat, prodName, brand, desc, link, imgurl, price, subcat) VALUES ('$id', '$store', '$storeuid', '$category', '$prodName', '$brand', '$prodDesc', '$link', '$image', '$price', '$subCategory')"; } if($limit >= $total){ echo '<br>Finished'; } else { echo<<<HTML <html><head> <meta http-equiv="refresh" content="2;URL=?start={$limit}"> </head><body> Done processing {$start} through {$limit}. Moving on to next set in 2 seconds. </body><html> HTML; } ?> 

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

Другой вариант: вы пытались правильно подготовить / связать свои запросы?

Вот два вопроса:

Память

На данный момент вы читаете полный файл в память с помощью file_get_contents () и разбираете его в объектной структуре с помощью SimpleXML. Оба действия загружают полный файл в память.

Лучшим решением является использование XMLReader:

 $reader = new XMLReader; $reader->open($file); $dom = new DOMDocument; $xpath = new DOMXpath($dom); // look for the first product element while ($reader->read() && $reader->localName !== 'product') { continue; } // while you have an product element while ($reader->localName === 'product') { // expand product element to a DOM node $node = $reader->expand($dom); // use XPath to fetch values from the node var_dump( $xpath->evaluate('string(@category)', $node), $xpath->evaluate('string(name)', $node), $xpath->evaluate('number(price)', $node) ); // move to the next product sibling $reader->next('product'); } 

Представление

Работа с большим количеством данных требует времени, делая это в последовательном порядке еще больше.

Перемещение сценария в командную строку может позаботиться о тайм-аутах. Возможно, также возможно увеличить предел с помощью `set_time_limit ().

Другой вариант – оптимизировать вставки, собрать некоторые записи и объединить их в одну вставку. Это уменьшает количество обращений / работы на сервере базы данных, но потребляет больше памяти. Вам нужно будет найти баланс.

 INSERT INTO table (field1, field2) VALUES (value1_1, value1_2), (value2_1, value2_2), ... 

Вы даже можете записать SQL в файл и использовать инструмент командной строки mysql для вставки записей. Это очень быстро, но имеет последствия для безопасности, потому что вам нужно использовать exec() .

Не могли бы вы проверить нижеуказанные 2 шага, если это поможет вам.

 1) Increase the default PHP execution time from 30 sec to a bigger one. ini_set('max_execution_time', 300000); 2) If fails please try to execute your code though cron job/back end. 

Раньше у меня была такая же проблема.

Разверните большой XML-файл на более мелкие файлы, такие как file1, file2, file3, а затем обработайте их.

Вы можете взорвать свой xml с помощью текстового редактора, который может открыть большие файлы. Не тратьте время на php при взломе файла.

edit: Я нахожу ответ для огромных xml-файлов. Я думаю, что это лучший ответ для этой цели. Разбор огромных файлов XML в PHP