为什么我的Perl脚本死于“内存不足”异常?

时间:2010-02-04 16:45:05

标签: database perl memory

我需要逐行读取200mb“空格”分隔文件,并将其内容收集到一个数组中。

每次运行脚本时,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 个答案:

答案 0 :(得分:6)

那是因为......你的内存不足了!

您不仅仅是存储200MB的数据。您正在为每一行创建一个新的列表数据结构,包含所有相关的开销,并为每个单词创建一堆单独的字符串对象,以及所有相关的开销。

编辑:作为我们在这里谈论的那种开销的一个例子,每个值(包括字符串)has the following overhead

/* 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;
};

每个Perl对象至少有4个32位值。

答案 1 :(得分:5)

通常这意味着你的Perl内存不足,但你可能没有用完系统内存。首先,有一些方法可以在perl debug guts doc中获得有关perl内存使用情况的更多信息 - 尽管你可能会发现自己正在重新编译perl。 (另请注意该文档中有关perl对记忆的渴望的警告......)

但是,许多操作系统可以为每个进程或每个用户设置内存限制。例如,如果您使用的是Linux(或其他POSIX系统),则可能需要更改ulimits。输入'ulimit -a'并查看你的内存大小;您的“最大内存大小”可能低于计算机内存 - 或者您的数据段大小有限。然后,您可以使用适当的选项重置它,例如ulimit -d 1048576,用于1GB数据段大小限制。

当然,还有另一种选择:如果您的情况允许,逐行处理文件。 (上面的示例代码可以用这种方式重写。)

答案 2 :(得分:4)

不是一次读取核心中的所有46,200,000个值,而是将cnai_all.csd中的数据描述为具有多行,这表明每行都可以独立处理。如果是这样,请使用

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

其中insert_row是您定义的用于将该行插入数据库的子。

请注意,split /\s/通常是个错误。 perlfunc documentation on 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 ];
}

答案 3 :(得分:2)

您的while循环无法从文件中读取。你应该

&LT; $ FH&GT;

或括号内的内容。

答案 4 :(得分:2)

最后,我找到了一个更适合我的问题的解决方案:

经过对我必须开发的其他解析器的一些研究后,我学会了 关于模块 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秒,内存不超过10MB。

答案 5 :(得分:0)

您正在使用的数据库可能具有批量导入功能。我会先试试。

如果在将每行放入数据库之前需要对每一行执行某些操作(假设操作不需要引用其他行),则应在处理完成后立即将该行插入数据库(转为AutoCommit } off,而不是试图将所有数据存储在内存中。

如果每行的处理取决于其他行中的信息,则可以使用Tie::File将输入文件视为一个行数组。同样,不要尝试将每行的内容存储在内存中。处理完成后,将其发送到数据库。