2012-09-26 79 views
2

我做了下面的Perl腳本來處理一些文件操作,但是它在運行時的運行速度太慢了。優化Perl腳本 - 在40GB +文件上運行速度太慢

我不太熟悉Perl(不是我的一種語言),所以有人可以幫助我識別和替換這個腳本的部分內容,因爲它處理了大約4000萬行,所以它會很慢。

數據在被管道輸送的格式爲:

col1|^|col2|^|col3|!| 
col1|^|col2|^|col3|!| 
... 40 million of these. 

的date_cols陣列腳本的這一部分之前計算並基本上保持包含在轉換前的格式的日期的列的索引。

以下是將爲每個輸入行執行的腳本部分。我已經清理了一點,添加評論,但讓我知道是否需要任何東西:

## Read from STDIN until no more lines are arailable. 
while (<STDIN>) 
{  
    ## Split by field delimiter 
    my @fields = split('\|\^\|', $_, -1); 

    ## Remove the terminating delimiter from the final field so it doesn't 
    ## interfere with date processing. 
    $fields[-1] = (split('\|!\|', $fields[-1], -1))[0]; 

    ## Cycle through all column numbres in date_cols and convert date 
    ## to yyyymmdd 
    foreach $col (@date_cols) 
    { 
     if ($fields[$col] ne "") 
     { 
      $fields[$col] = formatTime($fields[$col]); 
     } 
    } 

    print(join('This is an unprintable ASCII control code', @fields), "\n"); 
}   

## Format the input time to yyyymmdd from 'Dec 26 2012 12:00AM' like format. 
sub formatTime($) 
{ 
    my $col = shift;   

    if (substr($col, 4, 1) eq " ") { 
     substr($col, 4, 1) = "0"; 
    }  
    return substr($col, 7, 4).$months{substr($col, 0, 3)}.substr($col, 4, 2); 
} 
+0

你有沒有想過首先使用'csplit'之類的東西將文件拆分成碎片? – matchew

+0

這是如何工作的,並且假設我在所有的部分上運行這個腳本後它能夠重新組裝它們嗎? –

+0

我沒有看到任何明顯的低效率。 'print'函數是迄今爲止顯示速度最慢的,但我認爲這只是爲了調試目的。如果你正好運行這個代碼(減去'print'),它仍然很慢? *我有點懷疑,因爲'trim'子沒有在任何地方使用。* – dan1111

回答

3

如果純粹書面效率,我會寫你這樣的代碼:

sub run_loop { 
    local $/ = "|!|\n"; # set the record input terminator 
         # to the record seperator of our problem space 
    while (<STDIN>) {  
    # remove the seperator 
    chomp; 

    # Split by field delimiter 
    my @fields = split m/\|\^\|/, $_, -1; 

    # Cycle through all column numbres in date_cols and convert date 
    # to yyyymmdd 
    foreach $col (@date_cols) { 
     if ($fields[$col] ne "") { 
     # $fields[$col] = formatTime($fields[$col]); 
     my $temp = $fields[$col]; 
     if (substr($temp, 4, 1) eq " ") { 
      substr($temp, 4, 1) = "0"; 
     }  
     $fields[$col] = substr($temp, 7, 4).$months{substr($temp, 0, 3)}.substr($temp, 4, 2); 
     } 
    } 
    print join("\022", @fields) . "\n"; 
    } 
} 

的優化是:

  • 使用chomp
  • 刪除|!|\n串內聯formatTime分。

    子程序調用在Perl中非常昂貴。如果子必須非常有效地使用,則可以使用&subroutine(@args)語法禁用原型檢查。如果@args已被忽略,則當前參數@_對被叫子可見。這可能導致錯誤或額外的性能。明智地使用。 goto &subroutine;語法也可以使用,但是這與return(基本上是尾部調用)插手。不使用。

進一步的優化可能包括刪除哈希查找 %months,因爲哈希是昂貴的。

+0

我只是在10M迭代中用虛擬輸入(每行2個字段)運行此代碼。在我的筆記本電腦上,它在11s(帶有一個日期列)中完成,並在8s中刪除該foreach時結束。這實際上相當不錯,而且不需要優化。 IO *是限制因素。 @ pilcros的解決方案需要更長的時間,有18秒。 [引用需要] – amon

+0

謝謝你的幫助:) –

2

您必須對您的數據集進行比較的基準,但你可以扔了一個正則表達式。 (作出一切由您非常的正則表達式不友好的字段和記錄分隔符糟糕!)

my $i = 0; 
our %months = map { $_ => sprintf('%02d', ++$i) } qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); 

while (<DATA>) { 
    s! \|\^\| !\022!xg; # convert field separator 
    s/ \| !\| $ //xg;  # strip record terminator 
    s/\b(\w{3}) (\d|\d\d) (\d{4}) \d\d:\d\d[AP]M\b/${3} . $months{$1} . sprintf('%02d', $2) /eg; 
    print; 
} 

不會做你想要什麼,如果非@date_cols領域之一的日期正則表達式匹配。

+1

這是令人驚訝的優雅:)但是,'/ x'修飾符不會影響替換字符串中的空格。你應該在輸出字段分隔符的周圍包含空格,例如'·\ 022·',其中的點是空格。另外,不會在末尾錨定第二個替換'$'提高效率? – amon

+0

@amon,已更正,是,謝謝 – pilcrow

0

在我的工作中,有時我需要從350+前端grep errorlogs等。我使用的腳本模板我打電話 「SMP grep的」;)其簡單:

  1. stat文件,獲取文件長度
  2. 獲取 「塊長度」= file_length/num_processors
  3. Andjust塊開始和結束,因此他們開始/結束於「\ n」。只需read(),找到「\ n」並計算偏移量。
  4. fork()使num_processor工人,每個工作在自己的一塊

這可以幫助,如果你在你的grep或其他CPU操作使用正則表達式(如你的情況,我認爲)。管理員抱怨這個腳本吃的是磁盤吞吐量,但是如果服務器有8個CPU,這個腳本就是唯一的瓶頸=)另外,顯然如果你需要解析1周的數據,你可以在服務器之間進行分配。

明天我可以發佈代碼,如果感興趣。

+0

+1,我會看看這個謝謝:) –