优化Perl脚本 - 在40GB +文件上运行速度太慢

时间:2012-09-26 15:47:30

标签: perl date optimization data-conversion

我制作了以下Perl脚本来处理工作中的某些文件操作,但是它在运行时运行得太慢而无法投入生产。

我不太了解Perl(不是我的一种语言),所以有人可以帮我识别和替换这个脚本的部分内容,因为它处理了大约4000万行吗? / p>

传输的数据格式为:

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

3 个答案:

答案 0 :(得分: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中的子程序调用非常昂贵。如果必须非常有效地使用subs,则可以使用&subroutine(@args)语法禁用原型检查。如果省略@args,则当前参数@_对被调用的子可见。这可能会导致错误或其他性能。明智地使用。也可以使用goto &subroutine;语法,但这会干扰return(基本上是尾调用)。不要使用。

  •   
  进一步的优化可能包括删除散列查找%months,因为散列很昂贵。

答案 1 :(得分: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字段与日期正则表达式匹配,则无法执行您想要的操作。

答案 2 :(得分:0)

在我的工作中,有时我需要从350多个前端grep错误日志等。我使用脚本模板调用&#34; SMP grep&#34; ;)简单:

  1. stat文件,获取文件长度
  2. 获取&#34;块长度&#34; = file_length / num_processors
  3. Andjust chunk开始和结束,所以他们开始/结束&#34; \ n&#34;。只需read(),找到&#34; \ n&#34;并计算抵消额。
  4. fork()创建num_processor工作者,每个工作在自己的块上
  5. 如果你在grep或其他CPU操作中使用正则表达式,这可能会有所帮助(我认为)。管理员抱怨这个脚本会占用磁盘吞吐量,但如果服务器有8个CPU,那么这是唯一的瓶颈=)另外,显然如果你需要解析1周数据,你可以在服务器之间进行划分。

    明天如果有兴趣我可以发布代码。