大容量ファイルを追加/追加する簡単な方法[閉じる]

大容量ファイルを追加/追加する簡単な方法[閉じる]

かなり大きなファイルを1行ずつ読み、各行でいくつかの処理を実行し、結果を別のファイルに書き込むbashスクリプトがあります。現在私が使用しているecho結果ファイルの末尾に追加されますが、ファイルのサイズが大きくなるほど速度が遅くなります。だから私の質問は、大容量ファイルに行を追加する簡単な方法は何ですか?

ファイルに行が追加される順序は私とは関係がないので、次に追加します。スタートまたは終わりまたは何でもランダムファイルの場所。 RAMが多くのサーバーでスクリプトを実行しているため、結果を変数に保存して最後に内容全体を作成すると、より高速になります。これも私にとってはうまくいきます。

実際には2つのスクリプトがあり、ここにそれぞれの例を入れました。 (実際のスクリプトの一部ですが、単純化のためにいくつかの部分を削除しました。

while read line
do
    projectName=`echo $line | cut -d' ' -f1`
    filepath=`echo $line | cut -d' ' -f2`
    numbers=`echo $line | cut -d' ' -f3`
    linestart=`echo $numbers | cut -d: -f2`
    length=`echo $numbers | cut -d: -f3`
    lang=`echo $line | cut -d' ' -f9`
    cloneID=`echo $line | cut -d' ' -f10`
    cloneSubID=`echo $line | cut -d' ' -f11`
    minToken=`echo $line | cut -d' ' -f12`
    stride=`echo $line | cut -d' ' -f13`
    similarity=`echo $line | cut -d' ' -f14`
    currentLine=$linestart
    endLine=$((linestart + length))
    while [ $currentLine -lt $endLine ];
    do
        echo "$projectName, $filepath, $lang, $linestart, $currentLine, $cloneID, $cloneSubID, $minToken, $stride, $similarity"
        currentLine=$((currentLine + 1))
    done
done < $filename

上記のコードを使用する方法は次のとおりです。./script filename > outputfile

2番目のスクリプトは次のとおりです。

while read -r line;
do
    echo "$line" | grep -q FILE
    if [ $? = 0 ];
    then
        if [[ $line = *"$pattern"* ]];
        then
            line2=`echo "${line//$pattern1/$sub1}" | sed "s#^[^$sub1]*##"`
            newFilePath=`echo "${line2//$pattern2/$sub2}"`
            projectName=`echo $newFilePath | sed 's#/.*##'`
            localProjectPath=`echo $newFilePath | sed 's#^[^/]*##' | sed 's#/##'`
            cloneID=$cloneCounter
            revisedFile="revised-$postClusterFile-$projectName"
            overallRevisedFile="$cluster_dir/revised-overall-post-cluster"
            echo $projectName $localProjectPath $lang $cloneID $cloneSubID $minToken $stride $similarity >> $overallRevisedFile
            cloneSubID=$((cloneSubID + 1))
        fi
    fi
done < $cluster_dir/$postClusterFile

2番目のコードの使用法は次のとおりです。./script input output


修正する

まあ、明らかに犯人はバックティックの広範な使用です。最初のスクリプトは大幅に変更され、過去50分に比べて2分で実行されます。私はそれに非常に満足しています。次のコードを提供した@BinaryZebraに感謝します。

while read -r projectName filepath numbers a a a a a lang cloneID cloneSubID minToken stride similarity;
do
    IFS=':' read -r a linestart length <<<"$numbers"
    currentLine=$linestart
    endLine=$((linestart + length))

    while [ $currentLine -lt $endLine ]; do
        echo "$projectName, $filepath, $lang, $linestart, $currentLine, $cloneID, $cloneSubID, $minToken, $stride, $similarity"
        currentLine=$((currentLine + 1))
    done
done < $filename >>$outputfile

しかし、2番目のスクリプトでは、次のように修正しました(ここで実際のスクリプトもさらに追加しました)。

while read -r line;
do
  echo "$line" | grep -q FILE
  if [ $? = 0 ];
  then
    if [[ $line = *"$pattern"* ]];
    then
      IFS=$'\t' read -r a a filetest  <<< "$line"
      filetest="${filetest#*$pattern1}"
      projectName="${filetest%%/*}"
      localProjectPath="${filetest#*/}"
      cloneID=$cloneCounter
      revisedFile="revised-$postClusterFile-$projectName"
      echo $projectName $localProjectPath $lang $cloneID $cloneSubID $minToken $stride $similarity
      cloneSubID=$((cloneSubID + 1))
    fi
  else
    echo "This is a line: $line" | grep -q \n
    if [ $? = 0 ];
    then
       cloneCounter=$((cloneCounter + 1))
       cloneSubID=0
    fi
  fi
done < $cluster_dir/$postClusterFile >> $overallRevisedFile

以前よりはるかに速くなりました(7分対20分)。しかし、より速くする必要があり、より大きなテストでは、まだ速度が遅くなることを感じます。約24時間実行され、現在の出力サイズは200MBに近いです。出力ファイルが3GB程度になると予想しているので、2週間かかることもありますが、そんな余裕はありません。出力のサイズ/増加も非線形であるため、時間の経過とともに速度が遅くなります。

私ができる他のことがありますか、それともそれですか?

答え1

いくつかのアイデア:
1. - 各行で繰り返しカットを呼び出すのではなく、読み取りを活用します。
切り取る変数のリストは次の' 'とおりです。

projectName 1
filepath 2
numbers 3
lang 9
cloneID 10
cloneSubID 11
minToken 12
stride 13
similarity 14

これは次のことを読んで直接行うことができます。

while read -r projectName filepath numbers a a a a a lang cloneID cloneSubID minToken stride similarity;

生産ラインは長いが処理時間は短い。変数aは、未使用の値のスペースを埋めるために存在します。

2.- ':'で割る変数番号を再処理するには、次のようにすることができます(あなたの質問にはbashタグが付けられています)。

IFS=':' read -r a linestart length <<<"$numbers"

これにより、コードは次のように単純化されます。

while read -r projectName filepath numbers a a a a a lang cloneID cloneSubID minToken stride similarity;
do
    IFS=':' read -r a linestart length <<<"$numbers"

    currentLine=$linestart
    endLine=$((linestart + length))

    while [ $currentLine -lt $endLine ]; do
        echo "$projectName, $filepath, $lang, $linestart, $currentLine, $cloneID, $cloneSubID, $minToken, $stride, $similarity"
        currentLine=$((currentLine + 1))
    done
done < $filename >>$outputfile

3.- 2番目のスクリプトでは、sub1および/またはsub2変数が何であるかについての説明はありません。

4. - 一般的に言えば、スクリプトを一連の小さなスクリプトに分割できる場合は、各スクリプトの時間を測定して時間がかかる領域を見つけることができます。

5. - そして他の答えが示すように、ファイル(およびすべての中間結果)をメモリパーティションに配置すると、最初のファイルをより速く読み取ることができます。後でスクリプトを実行すると、メモリ内キャッシュから読み取られ、改善が隠されます。このガイド役に立ちます。

答え2

メモリ常駐ファイルシステムである/dev/shmにファイルを入れてみましたか?ファイルの読み取りと書き込みへのアクセス速度が向上します。最後に、shmから永続ディスクパーティションにファイルをコピーできます。

答え3

一つここで問題は、次のようにすることです。

while : loop
do    : processing
      echo "$results" >>output
done  <input

これにより、各反復の実行時間が劇的に増加する。outputopen()前回よりわずかに大きいオフセットで** edを繰り返します。私は言った慎重にあるからほぼ前のオフセットでファイルを開くのにかかる時間と、後のオフセットでファイルを開くのにかかる時間に差はありません。一部。そして毎回あなたはopen() O_APPEND前回ypuがあった場所より少し遠い場所でこれを行っています。かかる時間はディスク構成/基本ファイルシステムによって異なりますが、一部発生あたりのコストは、ファイルサイズが増加するにつれてある程度増加します。

おそらく、次のいずれかを実行する必要があります。open()そして維持するwrite()ループ寿命周期の説明です。次のようにすることもできます。

while : loop
do    : processing
      echo "$results"
done  <input >>output

これが主な理由ではないかもしれません。私にとっては、これが最も明白な理由であり、おそらく繰り返しの増加に直接関連していると思います。しかし、ループでは発生してはならない多くのことが起こっています。ループ反復ごとに10回以上のサブシェルデータ評価を実行しないでください。ベストプラクティスは何もしないことです。通常、ブランチなしで最初から最後まで完全に実行できるようにスタンドアロンシェルループを効率的に構築できない場合は、まったく実行しないでください。

代わりに、こことそこをスライスして評価を管理できるツールを使用して評価に集中する必要があります。TVシリーズ- これはよく書かれたパイプラインがどのように機能するかです。すべてのループ反復で多くの無限ループをキャッチする代わりに。次のように考えてみてください。

input |
(Single app single loop) |
(Single app single loop) |
(Single app single loop) |
output

これは、各単一ループが前のループと同時に実行されるパイプラインです。

しかし、あなたはむしろ以下が欲しいです:

input |
(Single app \
        (input slice|single app single loop);
        (input slice|single app single loop);
        (input slice|single app single loop);
 single loop) |
 output

これがサブシェルに依存するシェルループがどのように機能するかです。とにかくこれは効率的ではなく、入力と出力がバッファリングされないことも役に立ちません。

サブシェルは悪いことではありません。評価コンテキストを含む便利な方法です。ただし、より良い準備や条件付き入力または出力に必要なので、すべての種類のループの前後に適用することがほぼ常に最善です。より効率的なサイクルに適しています。これらのタスクを繰り返し実行するのではなく、時間をかけて最初に正しく設定してから、起動した後は他のタスクを実行しないでください。

答え4

  • 大きなファイルは、小さなファイルよりも少し遅くなる可能性があります。これは、データが多いためだけではありません。もしファイル 第二ファイルサイズの1000倍 、全体の処理時間が1001倍または1002倍長くなる可能性があります。
  • 繰り返すたびに出力ファイルを再度開いて終了を見つけた場合、パフォーマンスが低下します。 2番目のスクリプトを変更してみてください。

    -r 行を読むとき
    する
                echo "$projectName $localProjectPath … $stride $similarity"
    完了<"$cluster_dir/$postClusterFile">>"$overallRevisedファイル"

    既存のファイルにコンテンツを追加しない場合は、その行に(代わりに)$overallRevisedFile入力するだけです。> "$overallRevisedFile">>done

    しかし、大きな影響を与えるようには思えません。

  • ループ全体の標準出力をリダイレクトしたくない場合は、次のようにします。

    -r 行を読むとき
    する
                echo "$projectName $localProjectPath … $stride $similarity">&3
    完了<"$cluster_dir/$postClusterFile"  3>>"$overallRevisedFile"

    複数のループで出力ファイルにアクセスする必要がある場合は、次のようにします。

    3>>"$overallRevisedFile" 実行
    -r 行を読むとき
    する
                echo "$projectName $localProjectPath … $stride $similarity">&3
    完了<"$cluster_dir/$postClusterFile"
    (他のコード) >&3
    実行3>&-
  • スクリプトをより良くすることができますが、必ずしも高速ではないいくつかのことがあります。

    • 特別な理由がない限り、必ずシェル変数参照(例:、および)を引用する必要"$line"があります"$cluster_dir""$postClusterFile""$overallRevisedFile"確かにあなたは何をしているのか知っています。
    • $(command)ほぼ同じで、より読みやすいと広く考えられています。`command`
    • (少なくとも)echo不要なものが一つあります。

      newFilePath=`echo "${line2//$pattern2/$sub2}"`
      

      次のように単純化できます。

      newFilePath="${line2//$pattern2/$sub2}"
      

関連情報