2012-09-26 81 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,我会看看这个谢谢:) –