Почему мой Perl-скрипт умирает с исключением «недостаточно памяти»? - PullRequest
1 голос
/ 04 февраля 2010

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

Каждый раз, когда я запускаю скрипт, Perl выдает исключение «недостаточно памяти», но я не понимаю, почему!

Несколько советов, пожалуйста?

#!/usr/bin/perl -w
use strict;
use warnings;

open my $fh, "<", "../cnai_all.csd";
my @parse = ();

while (<$fh>) {
     my @words = split(/\s/,$_);
     push (@parse, \@words);
}

print scalar @parse;

файл cnai выглядит следующим образом: он содержит 11000 строк и 4200 значений, разделенных «пробелом» на строку.

VALUE_GROUP_A VALUE_GROUP_B VALUE_GROUP_C 
VALUE_GROUP_A VALUE_GROUP_B VALUE_GROUP_C 
VALUE_GROUP_A VALUE_GROUP_B VALUE_GROUP_C 
VALUE_GROUP_A VALUE_GROUP_B VALUE_GROUP_C 

Приведенный выше код является просто урезанным образцом.
Последний скрипт сохранит все значения в хэше и позже запишет его в базу данных.

Но сначала я должен решить эту проблему с памятью!

Ответы [ 6 ]

6 голосов
/ 04 февраля 2010

Это было бы потому, что ... у вас заканчивается память!

Вы не просто храните 200 МБ данных. Вы создаете новую структуру данных списка для каждой строки со всеми связанными с ней издержками, а также создаете группу отдельных строковых объектов для каждого слова со всеми связанными с ними издержками.

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

/* start with 2 sv-head building blocks */
#define _SV_HEAD(ptrtype) \
    ptrtype sv_any;     /* pointer to body */   \
    U32     sv_refcnt;  /* how many references to us */ \
    U32     sv_flags    /* what we are */

#define _SV_HEAD_UNION \
    union {             \
    char*   svu_pv;     /* pointer to malloced string */    \
    IV      svu_iv;         \
    UV      svu_uv;         \
    SV*     svu_rv;     /* pointer to another SV */     \
    SV**    svu_array;      \
    HE**    svu_hash;       \
    GP* svu_gp;         \
    }   sv_u


struct STRUCT_SV {      /* struct sv { */
    _SV_HEAD(void*);
    _SV_HEAD_UNION;
};

Так что это как минимум 4 32-битных значения на объект Perl.

5 голосов
/ 04 февраля 2010

Обычно это означает, что у вас недостаточно памяти для Perl, но возможно, что у вас не хватает системной памяти. Во-первых, есть способы получить дополнительную информацию об использовании памяти perl в perl отладочной документации doc - хотя вы можете перекомпилировать perl. (Также обратите внимание на предупреждение в этом документе о жажде памяти в Perl ...)

Однако во многих операционных системах возможно ограничение пределов памяти для каждого процесса или для каждого пользователя. Например, если вы используете Linux (или другую систему POSIX), вам может потребоваться изменить свои ограничения. Наберите 'ulimit -a' и посмотрите, сколько у вас памяти; Возможно, ваш «максимальный объем памяти» ниже объема памяти на вашем компьютере - или у вас ограниченный размер сегмента данных. Затем вы можете сбросить его с помощью соответствующей опции, например, ulimit -d 1048576 для ограничения размера сегмента данных 1 ГБ.

Конечно, есть еще один вариант: построчно обрабатывать файл, если ваша ситуация это позволяет. (Пример кода выше можно переписать таким образом.)

4 голосов
/ 05 февраля 2010

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

while (<$fh>) {
  my @words = split /\s/, $_;
  insert_row \@words;
}

где insert_row - это подпрограмма, которую вы определите, чтобы вставить эту строку в вашу базу данных.

Обратите внимание, что split /\s/ часто является ошибкой. Документация perlfunc для split объясняет:

В особом случае указание ШАБЛОНА пробела (' ') будет разбито на пустое пространство, как split без аргументов. Таким образом, split(' ') может использоваться для эмуляции поведения по умолчанию в awk, тогда как split(/ /) даст вам столько нулевых начальных полей, сколько имеется начальных пробелов. split на /\s+/ походит на split(' ') за исключением того, что любой начальный пробел создает нулевое первое поле. split без аргументов действительно split(' ', $_) внутренне.

В штатном случае все нормально:

  DB<1> x split /\s/, "foo bar baz"
0  'foo'
1  'bar'
2  'baz'

Но что, если между полями есть несколько пробелов? Означает ли это пустое поле или просто «широкий» разделитель?

  DB<2> x split /\s/, "foo  bar baz"
0  'foo'
1  ''
2  'bar'
3  'baz'

А как насчет пробелов?

  DB<3> x split /\s/, " foo bar baz"
0  ''
1  'foo'
2  'bar'
3  'baz'

Поведение по умолчанию split не является произвольным. Пусть инструмент работает на вас!

while (<$fh>) {
  insert_row [ split ];
}
2 голосов
/ 15 февраля 2010

Наконец-то я нашел более подходящее решение для моей проблемы:

После некоторых исследований других парсеров, которые мне пришлось разработать, я узнал, о модуле DBD :: CSV .

DBD :: CSV позволяет мне выбирать только необходимые столбцы (из> 4000) полей с пробелами. Это значительно уменьшает использование памяти и производительность.

Больше на DBD-CSV @ CPAN.org

Благодаря gbacon я изменил свою стратегию: читать весь файл за один раз, читать его по частям. DBD :: CSV делает это возможным без особого кодирования.

#!/usr/bin/perl  -w

use strict;
use warnings;
use DBI;
## -------------------------------------------------------------------------##

## -------------------------------------------------------------------------##
## SET GLOBAL CONFIG #############
my $globalConfig = {
                _DIR => qq{../Data},
                _FILES => {
                    'cnai_all.csd'   => '_TEST'
                    }               
                };
## -------------------------------------------------------------------------##


## -------------------------------------------------------------------------##
my $sTime = time();

my $sepChar = " ";
my $csv_dbh = DBI->connect("DBI:CSV:f_dir=".$globalConfig->{_DIR}.";");

$csv_dbh->{csv_eol} ="\n";
#$csv_dbh->{csv_quote_char} ="'";
#$csv_dbh->{csv_escape_char} ="\\";
$csv_dbh->{csv_null} = 1;
$csv_dbh->{csv_quote_char} = '"';
$csv_dbh->{csv_escape_char} = '"';
$csv_dbh->{csv_sep_char} = "$sepChar";
$csv_dbh->{csv_always_quote} = 0;
$csv_dbh->{csv_quote_space} = 0;
$csv_dbh->{csv_binary} = 0;
$csv_dbh->{csv_keep_meta_info} = 0;
$csv_dbh->{csv_allow_loose_quotes} = 0;
$csv_dbh->{csv_allow_loose_escapes} = 0;
$csv_dbh->{csv_allow_whitespace} = 0;
$csv_dbh->{csv_blank_is_undef} = 0;
$csv_dbh->{csv_empty_is_undef} = 0;
$csv_dbh->{csv_verbatim} = 0;
$csv_dbh->{csv_auto_diag} = 0;


my @list = $csv_dbh->func('list_tables');
my $sth = $csv_dbh->prepare("SELECT CELL,NW,BSC,n_cell_0 FROM cnai_all.tmp");


#print join ("\n",@list);

print "\n-------------------\n";

$sth->execute();
while (my $row = $sth->fetchrow_hashref) {
    # just print a hash refrence
    print "$row\n";
}
$sth->finish();

print "\n finish after ".(time()-$sTime)." sec ";

На моей машине это работает примерно за 20 секунд и использует не более 10 МБ памяти.

2 голосов
/ 04 февраля 2010

Ваш цикл while не читает из файла. Вы должны иметь

<$ ОГО>

или что-то в скобках.

0 голосов
/ 05 февраля 2010

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

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

Если обработка каждой строки зависит от информации в других строках, тогда вы можете использовать Tie :: File для обработки входного файла как массива строк. Опять же, не пытайтесь хранить содержимое каждой строки в памяти. После завершения обработки отправьте его в базу данных.

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