Как и многие другие, я боролся с использованием памяти PHPExcel при чтении файла (чтобы преобразовать его в MySQL).
Конечно, я пробовал обычные вещи, упомянутые в разных местах, и смог повысить эффективность памяти как минимум на 40%. Это включает использование пользовательского класса читателя chunked, перемещение экземпляра читателя chunked за пределы цикла чтения и т. Д.
На моем тестовом сервере у меня 16 ГБ ОЗУ, а в PHP выделено 2 ГБ. PHPExcel будет работать с файлами размером менее 200Кб (медленно, но верно). При достижении определенного размера скрипт не может просто вывести «Killed» в оболочку. Логи показали, что ядро убило PHP, потому что оно использовало слишком много памяти. Наблюдая за использованием процессора и памяти с помощью команды top, я вижу, что память свободна и свободен от подкачки, когда память используется, а подкачка — взлетает.
Прочитав ЛОТ о PHPExcel и просмотрев некоторые исходные файлы, я пришел к выводу, что по каждой ячейке хранится много данных, которые не нужны при обработке только текста. С помощью:
$objReader->setReadDataOnly(true);
немного помогает, но на самом деле не так уж много … Тем не менее, использование читателя chunked и установка размера чанка на что-то маленькое, а затем использование unset () для очистки больших переменных теоретически должно работать. Я знаю, что PHPExcel должен читать весь файл каждый раз, но он не должен хранить его в памяти, верно?
Вот код, который я сейчас использую:
<?php
date_default_timezone_set("America/New_York");
set_time_limit(7200);
ini_set('memory_limit', '2048M');
include_once("classes/PHPExcel/PHPExcel/IOFactory.php");
$inputFileName = "/PATH/TO/FILE.xlsx";
$inputFileType = PHPExcel_IOFactory::identify($inputFileName);
$worksheetName = "Sheet1";
class chunkReadFilter implements PHPExcel_Reader_IReadFilter
{
private $_startRow = 0;
private $_endRow = 0;
public function __construct($startRow, $chunkSize)
{
$this->_startRow = $startRow;
$this->_endRow = $startRow + $chunkSize;
}
public function setRows($startRow, $chunkSize)
{
$this->_startRow = $startRow;
$this->_endRow = $startRow + $chunkSize;
}
public function readCell($column, $row, $worksheetName = '')
{
if (($row == 1) || ($row >= $this->_startRow && $row < $this->_endRow))
{
return true;
}
return false;
}
}$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setReadDataOnly(true);
$chunkSize = 1000;
echo "Got here 1\n";
$chunkFilter = new chunkReadFilter(2,$chunkSize);for ($startRow = 2; $startRow <= 378767; $startRow += $chunkSize)
{
$chunkFilter->setRows($startRow, $chunkSize);
$objReader->setReadFilter($chunkFilter);
echo "Got here 2\n";
$objPHPExcel = $objReader->load($inputFileName);
echo "Got here 3\n";
$sheet = $objPHPExcel->getSheetByName($worksheetName);
echo "Got here 4\n";
$highestRow = $sheet->getHighestRow();
$highestColumn = $sheet->getHighestColumn();
echo "Got here 5\n";
$sheetData = $sheet->rangeToArray("A".$startRow.":".$highestColumn.$highestRow, NULL, TRUE, FALSE);
print_r($sheetData);
echo "\n\n";
}
?>
Какие выводы:
[USER@BOX Directory]# php PhpExcelBigFileTest.php
Got here 1
Got here 2
Killed
Это приводит к вопросу: пытается ли PHPExcel загрузить весь файл в память независимо от моего фильтра? Если это так, то почему PHP не останавливает его при использовании памяти 2G, но позволяет ему стать настолько плохим, что ядро должно убить PHP?
В настоящее время PHPExcel использует SimpleXML для чтения форматов на основе XML, таких как OfficeOpenXML (xlsx), OASIS (.odc) и Gnumeric, а не для более эффективного использования памяти XMLReader. Это означает, что каждый файл XML-файла в заархивированном архиве непосредственно загружается в память PHP для анализа и создания объекта PHPExcel. В то время как разбиение на ячейки уменьшает объем памяти, используемой объектом PHPExcel, за счет уменьшения количества ячеек, которые он содержит, до тех, которые определены фильтром чтения, ему все равно требуется, чтобы весь файл был загружен в память для его анализа SimpleXML.
Команда разработчиков изучила потоковую передачу данных непосредственно из сжатого архива в PHP-читатель pull-parser PHP, и первоначальные эксперименты показали, что это очень эффективно использует память; но это также важная часть переписывания кода, чтобы переориентировать читателей электронных таблиц на использование этого метода; и с ограниченным ресурсом разработки и ограниченным доступным временем для выполнения работы, эта задача не является легкой задачей.
В дополнение к сокращению памяти путем загрузки только подмножества ячеек в объект PHPExcel, вы также можете рассмотреть кэширование ячеек. Это описано в документация и позволяет хранить ячейки таким образом, чтобы уменьшить объем занимаемой ими памяти. Для разных систем предусмотрены разные методы, и объем сохраняемой памяти будет варьироваться в зависимости от версии и конфигурации PHP, поэтому вам необходимо определить, какие методы наиболее подходят для вашей собственной системы. С использованием кэширования ячеек также зависит скорость. Обычно SQLite является наиболее эффективным с точки зрения памяти, но также и одним из самых медленных методов.
Других решений пока нет …