web-dev-qa-db-ja.com

なぜテールファイルですか? tr(パイプライン)はsedまたはPerlよりも多くの行で高速ですか?

次のように、約100万行のファイルがあります。

"ID" "1" "2"
"00000687" 0 1
"00000421" 1 0
"00000421" 1 0
"00000421" 1 0

最後の行は100万回以上繰り返されました。 この質問 からインスピレーションを得て、提案されているソリューションのいくつかを試して、どちらが速いかを確認しました。 1つのプロセスのみを使用するソリューションは、1つのプロセスしか使用しないため、パイプラインを使用するソリューションよりも高速であると期待していました。しかし、それらは私のテストの結果です:

  • tail -n +2 file.txt | tr -d \"

    $ time tail -n +2 file.txt | tr -d \" 1> /dev/null
    
    real    0m0,032s
    user    0m0,020s
    sys     0m0,028s
    
  • sed '1d;s/"//g' file.txt

    $ time sed '1d;s/"//g' file.txt 1> /dev/null
    
    real    0m0,410s
    user    0m0,399s
    sys     0m0,011s
    
  • Perl -ne ' { s/"//g; print if $. > 1 }' file.txt

    $ time Perl -ne ' { s/"//g; print if $. > 1 }' file.txt 1> /dev/null
    
    real    0m0,379s
    user    0m0,367s
    sys     0m0,013s
    

私は何度もテストを繰り返しましたが、常に同じような数を取得しています。ご覧のように、 tail -n +2 file.txt | tr -d \"は他のものよりはるかに速いです。どうして?

9

要するに、行われている作業の量です。

きみの tail | trコマンドは、次のことを実行します。

  • in tail
    • 改行まで読んでください。
    • 改行を気にせずに、残りのすべてを出力します。
  • trで、改行を気にせずに読み取り、「」(固定文字)以外のすべてを出力します。

sedコマンドは、指定されたスクリプトを解釈した後、次のようになります。

  • 改行まで読み、入力を蓄積します。
  • これが最初の行の場合は削除します。
  • 正規表現を解釈した後、すべての二重引用符を何も置き換えません。
  • 処理された行を出力します。
  • ファイルの終わりまでループします。

与えられたスクリプトを解釈した後、Perlコマンドは次のようになります。

  • 改行まで読み、入力を蓄積します。
  • 正規表現を解釈した後、すべての二重引用符を何も置き換えません。
  • これが最初の行でない場合、処理された行を出力します。
  • ファイルの終わりまでループします。

改行を探すことは、大量の入力に対して高価になります。

11
Stephen Kitt

主な理由は、Perlとsedが各行を個別に処理するためです。

Perlがより大きなブロックで入力を処理し、それを少し簡略化すると(注を参照)、はるかに高速にすることができますが、trほど高速ではありません。

time Perl -ne ' { s/"//g; print if $. > 1 }' file.txt 1> /dev/null

real    0m0.617s
user    0m0.612s
sys     0m0.005s

time Perl -pe 'BEGIN{<>;$/=\40960} s/"//g' file.txt >/dev/null

real    0m0.186s
user    0m0.177s
sys     0m0.009s

time tail -n +2 file.txt | tr -d \" 1> /dev/null

real    0m0.033s
user    0m0.031s
sys     0m0.023s

注:Perl -ne '... if $. > 1'またはawk 'NR == 1 { ... } /foo/ { ... }'は使用しないでください。

代わりにBEGIN{<>}およびBEGIN{getline}を使用してください。

最初の行を読んだら、後続の行が最初の行にならないことを確信できます。何度も確認する必要はありません。

7
pizdelect

tail.cのtail_lines():

_      /* Use file_lines only if FD refers to a regular file for
         which lseek (... SEEK_END) works.  */

      if ( ! presume_input_pipe
           && S_ISREG (stats.st_mode)
           && (start_pos = lseek (fd, 0, SEEK_CUR)) != -1
           && start_pos < (end_pos = lseek (fd, 0, SEEK_END)))
_

このend_pos = lseek (fd, 0, SEEK_END)は、ファイルの内容がスキップされる場所です。 file_lines()には、改行を数える逆方向スキャンがあります。

lseek()は、読み取り/書き込み用にファイルオフセットを再配置するための非常に単純なシステムコールです。


ああ、このQでは微妙なところを見逃してしまったようです;)それは、ラインワイズとブロックワイズの両方を読むことです。通常、複数のパスを1つの複雑なパスに結合することをお勧めします。しかし、ここではアルゴリズムは最初の改行だけを必要とします。

Oleのsysread()を使用した2部構成のPerlスクリプトは、最初の改行の検索から最大ブロックの読み取りに切り替える方法を示しています。

tailが通常の逆方向に機能する場合、最後のブロックを読み取り、改行を数えます。そこから印刷するか、最後から2番目のブロックを読み取ります。

2
rastafile

Perlを使用したい気がしますが、遅すぎます。

Perlは一般的なツールであり、trのような特殊なツールほど高速ではありません。ただし、近づくことができます。

$ tail -n +2 file.txt | tr -d \" >/dev/null;
real    0m0.040s
user    0m0.030s
sys     0m0.032s

$ Perl -e 'while(sysread(STDIN,$b,1)) {$b eq "\n" and last}
           while(sysread(STDIN,$b,131072)) {
             $b=~tr/\"//d; print $b
           }' < file.txt > /dev/null;
real    0m0.049s
user    0m0.045s
sys     0m0.004s

あなたはtailを避けてさらに速く進むことができます:

$ time (read; tr -d \") < file.txt >/dev/null
real    0m0.033s
user    0m0.021s
sys     0m0.012s
2
Ole Tange