2010-02-04 101 views
1

我需要逐行讀取一個200MB「分隔」文件並將其內容收集到一個數組中。爲什麼我的Perl腳本死於「內存不足」異常?

我每次運行該腳本,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

代碼說'while(<$fh>)',但它沒有顯示在標記中。 – mob 2010-02-04 16:50:53

+0

@ floppy-doo請編輯您的問題,告訴我們cnai_all.csd的內容是什麼樣子的? – 2010-02-04 16:55:21

+0

另請參閱:http://stackoverflow.com/questions/1663498/finding-a-perl-memory-leak/ – Ether 2010-02-04 21:53:30

回答

2

最後我發現我的問題一個更合適的解決方案:

一些研究其他解析器我已經開發後,我瞭解到 有關模塊DBD :: CSV

DBD :: CSV讓我選擇「空白」 -seperated場只有需要的列(滿分> 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的內存。

2

while循環不從文件中讀取。你應該有

< $ FH >

或括號裏面的東西。

+1

Markdown格式化問題。編輯。 – 2010-02-04 16:49:54

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

所以這是至少每4個32位值Perl對象。

+1

但是,這不應該*那*很多開銷,應該嗎?除非它是一個真正墮落的文件,否則我會感到驚訝,如果它可以比內存中的有效大小增加一倍以上, – fennec 2010-02-04 17:02:48

+0

胡虎虎...退化文件.... – 2010-02-04 17:08:34

+0

那些不是列表結構。他們是**數組**。在大多數語言中,沒有區別。在Perl中,有一個。 – daotoad 2010-02-05 03:26:27

5

通常這意味着您的Perl內存不足,但可能沒有用完系統內存。首先,有許多方法可以在perl debug guts doc中獲得有關perl內存使用情況的更多信息 - 儘管您可能會發現自己正在重新編譯perl。 (還要注意在DOC有關Perl的飢餓對內存的警告......)

然而,許多操作系統會是可能的內存限制每個進程或每個用戶設置。例如,如果您使用的是Linux(或其他POSIX系統),則可能需要更改您的ulimits。輸入'ulimit -a'並查看你的內存大小;有可能你的'最大內存大小'低於你機器的內存 - 或者你的數據段大小是有限的。然後,您可以使用適當的選項重置它,例如,對於1GB數據段大小限制,ulimit -d 1048576。

當然,還有另一種選擇:處理文件中的行由行,如果條件允許它。 (上面的例子的代碼可以以這樣的方式進行重寫。)

0

您使用可能該數據庫具有一個批量導入功能。我會先嚐試一下。

如果您需要將其放入數據庫中(假設操作不需要引用其他行)之前,做每一行的東西,你應該儘快插入一行到數據庫中處理完成(轉AutoCommit關閉)而不是試圖將所有數據存儲在內存中。

如果每行的處理依賴於其它行中的信息,則可以使用Tie::File治療輸入文件作爲線的陣列。再次,不要嘗試將每行的內容存儲在內存中。處理完成後,將其發送到數據庫。

+0

問題在於從某種程度上提取數據(哈,我聽過更強的術語)錯綜複雜的逗號分隔格式。由於文件中的最後一行(每行12'000行+ 4,500個字段)可以改變/增加第一行中「省略」的內容。由於該文件的內容轉到12個不同的數據庫表,我建議以兩遍通過該文件。首先分成12組,在第二輪中將每個文件收集/壓縮成記錄。我會閱讀Tie :: File,thx的建議。 – lexu 2010-02-06 06:38:12

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(/ /)就會給你無數的空初始場,因爲有前導空格。上/\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 ]; 
}