我需要逐行读取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
上面的代码只是一个精简的样本。
最终的脚本将所有值存储在哈希中,稍后将其写入数据库。
但首先,我必须解决内存问题!
答案 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)。这样可以减少内存使用量,并且运行良好。
感谢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将输入文件视为一个行数组。同样,不要尝试将每行的内容存储在内存中。处理完成后,将其发送到数据库。