2

次のPerlスクリプトを作成して、作業中のファイル操作を処理しましたが、実行速度が遅すぎて本番環境に移行できません。

私はPerlをよく知らないので(私の言語の1つではありません)、誰かがこのスクリプトの処理が最大4,000万行であることを考えると遅い部分を特定して置き換えるのを手伝ってもらえますか?

パイプされるデータの形式は次のとおりです。

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

3 に答える 3

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あります。

于 2012-09-26T16:43:58.417 に答える
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つが日付の正規表現と一致する場合、必要な処理を実行しません。

于 2012-09-26T16:42:43.127 に答える
0

私の仕事では、350以上のフロントエンドからエラーログなどをgrepする必要がある場合があります。私は「SMPgrep」を呼び出すスクリプトテンプレートを使用します;)その単純な:

  1. statファイル、ファイルの長さを取得
  2. 「チャンクの長さ」を取得=file_length/ num_processors
  3. Andjustチャンクは開始および終了するため、「\n」で開始/終了します。ただread()、「\ n」を見つけて、オフセットを計算します。
  4. fork()それぞれが独自のチャンクで動作するnum_processorワーカーを作成します

これは、grepまたは他のCPU操作で正規表現を使用する場合に役立ちます(私が思うに)。このスクリプトに不満を言う管理者はディスクスループットを消費しますが、サーバーに8つのCPUがある場合の唯一のボトルネック=)また、明らかに1週間のデータを解析する必要がある場合は、サーバー間で分割できます。

明日、興味があればコードを投稿できます。

于 2012-09-26T18:14:27.477 に答える