Проблемы с чтением огромного файла CSV с помощью php fgetcsv - понимание потребления памяти

Доброе утро, я на самом деле прохожу несколько тяжелых уроков, пытаясь обрабатывать огромные CSV-файлы размером до 4 ГБ.

Цель состоит в том, чтобы искать некоторые элементы в CSV-файле (канал данных Amazon) по заданному узлу просмотра, а также по некоторым заданным идентификаторам элементов (ASIN). Чтобы получить смесь существующих предметов (в моей базе данных) плюс несколько дополнительных новых предметов, так как время от времени предметы исчезают на рынке. Я также фильтрую названия элементов, потому что многие элементы используют одно и то же.

Я читал здесь много советов и, наконец, решил использовать php fgetcsv() и подумал, что эта функция не будет исчерпывать память, так как она читает файл построчно. Но что бы я ни пытался, мне всегда не хватает памяти. Я не могу понять, почему мой код использует так много памяти.

Я установил ограничение памяти на 4096 МБ, ограничение по времени равно 0. Сервер имеет 64 ГБ оперативной памяти и два жестких диска SSD.

Может кто-нибудь проверить мой фрагмент кода и объяснить, как это возможно, что у меня заканчивается память и, что более важно, как используется память?

private function performSearchByASINs()
{
    $found = 0;
    $needed = 0;
    $minimum = 84;
    if(is_array($this->searchASINs) && !empty($this->searchASINs))
    {
        $needed = count($this->searchASINs);
    }
    if($this->searchFeed == NULL || $this->searchFeed == '')
    {
        return false;
    }
    $csv = fopen($this->searchFeed, 'r');
    if($csv)
    {
        $l = 0;
        $title_array = array();
        while(($line = fgetcsv($csv, 0, ',', '"')) !== false)
        {
            $header = array();
            if(trim($line[6]) != '')
            {
                if($l == 0)
                {
                    $header = $line;
                }
                else
                {
                    $asin = $line[0];
                    $title = $this->prepTitleDesc($line[6]);
                    if(is_array($this->searchASINs) 
                    && !empty($this->searchASINs) 
                    && in_array($asin, $this->searchASINs)) //search for existing items to get them updated
                    {
                        $add = true;
                        if(in_array($title, $title_array))
                        {
                            $add = false; 
                        }
                        if($add === true)
                        {
                            $this->itemsByASIN[$asin] = new stdClass();
                            foreach($header as $k => $key)
                            {
                                if(isset($line[$k]))
                                {
                                    $this->itemsByASIN[$asin]->$key = trim(strip_tags($line[$k], '<br><br/><ul><li>'));
                                }
                            }
                            $title_array[] = $title;
                            $found++;
                        }
                    }
                    if(($line[20] == $this->bnid || $line[21] == $this->bnid) 
                    && count($this->itemsByKey) < $minimum 
                    && !isset($this->itemsByASIN[$asin])) // searching for new items
                    {
                        $add = true;
                        if(in_array($title, $title_array))
                        {
                           $add = false;
                        }
                        if($add === true)
                        {
                            $this->itemsByKey[$asin] = new stdClass();
                            foreach($header as $k => $key)
                            {
                                if(isset($line[$k]))
                                {
                                    $this->itemsByKey[$asin]->$key = trim(strip_tags($line[$k], '<br><br/><ul><li>'));                                
                                }
                            }
                            $title_array[] = $title;
                            $found++;
                        }
                    }
                }
                $l++;
                if($l > 200000 || $found == $minimum)
                {
                    break;
                }
            }
        }
        fclose($csv);
    }
}

person Thomas Tonius    schedule 17.09.2015    source источник


Ответы (3)


Я знаю, что мой ответ немного запоздал, но у меня была аналогичная проблема с fgets() и вещами, основанными на fgets(), такими как функция SplFileObject->current(). В моем случае это было в системе Windows при попытке прочитать файл +800MB. Я думаю, что fgets() не освобождает память предыдущей строки в цикле. Таким образом, каждая прочитанная строка оставалась в памяти и приводила к фатальной ошибке нехватки памяти. Вместо этого я исправил это, используя fread($lineLength), но это немного сложнее, так как вы должны указать длину.

person lorenzobe    schedule 20.05.2016

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

person MACMAN    schedule 17.09.2015
comment
Что ж. Я просто подумал, что производительность в целом будет лучше без использования базы данных, а вместо этого с использованием простых файлов csv, поскольку Amazon требует регулярного обновления данных (по крайней мере, каждые 24 часа), что означает сравнение basefeed с updatefeeds который может появляться каждые 30 минут - person Thomas Tonius; 17.09.2015
comment
Если возможно, попробуйте разделить большой файл на несколько файлов. Ищите инструменты, которые могут это сделать - person MACMAN; 17.09.2015
comment
Хм, я все еще надеюсь, что если кто-то сможет объяснить, как подробно используется память, ее можно обработать в одном файле. У меня уже есть 82 файла для обработки. - person Thomas Tonius; 17.09.2015
comment
Может эта ветка вам поможет. stackoverflow.com/questions/5249279/ - person MACMAN; 17.09.2015

Вы пробовали это? SplFileObject::fgetcsv

<?php
$file = new SplFileObject("data.csv");
while (!$file->eof()) {
    //your code here
}
?>

У вас заканчивается память, потому что вы используете переменные, и вы никогда не выполняете unset(); и используете слишком много вложенных foreach. Вы можете уменьшить этот код в большем количестве функций. Решение должно быть, вместо этого используйте настоящую базу данных.

person Elias Nicolas    schedule 17.09.2015
comment
Я не хочу использовать базу данных, так как мне приходится регулярно обновлять эти каналы. Я использую три массива, которые собирают данные и могут содержать до 200 значений (каждый). Четвертый массив — это $header, который сбрасывается для каждой строки, и я не сбрасываю переменные, поскольку они сбрасываются после каждой строки, прочитанной в цикле while. Но спасибо за публикацию SplFileObject - я собираюсь прочитать руководство - person Thomas Tonius; 17.09.2015
comment
Я пробовал это, но не помогает, но все равно спасибо! - person Thomas Tonius; 17.09.2015