
次のような約100万行のファイルがあります。
"ID" "1" "2"
"00000687" 0 1
"00000421" 1 0
"00000421" 1 0
"00000421" 1 0
最後の行は百万回以上繰り返された。 ~からインスピレーションを得るこの問題、提案された解決策のいくつかを試して、どちらが速いかを確認しました。 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 \"
はいはるかに早く他人より。なぜ?
答え1
これは、実行されるタスクの量によって決まります。
あなたのtail | tr
コマンドは次のように終了します。
- 存在する
tail
:- 改行まで読みます。
- 改行の有無にかかわらず、残りの内容をすべて出力します。
- in は
tr
読み、改行文字を気にせず、 '''(固定文字)を除くすべてを出力します。
sed
与えられたスクリプトを解釈した後、コマンドは次のことを行います。
- 改行まで読み、入力を累積します。
- 最初の行の場合は削除してください。
- 正規表現を解釈した後、すべての二重引用符を空白スペースに置き換えます。
- 処理されたラインを出力します。
- ファイルの最後まで繰り返します。
与えられたスクリプトを解釈した後、Perlコマンドは次のことを行います。
- 改行まで読み、入力を累積します。
- 正規表現を解釈した後、すべての二重引用符を空白スペースに置き換えます。
- 最初の行でない場合は、処理された行を出力します。
- ファイルの最後まで繰り返します。
大量の入力のため、改行文字を見つけるのに費用がかかります。
答え2
これは主に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}
。
最初の行を読んだ後、次の行がもはや最初の行ではないことを確認できます。もう一度確認する必要はありません。
答え3
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()は、読み取り/書き込みのためにファイルオフセットを再配置するために使用される非常に単純なシステムコールです。
ああ、質問の微妙さを逃したようです。 ;) 1行ずつ読み、1ブロックずつ読み込むことがすべてです。複数のチャンネルを 1 つの複雑なチャンネルに組み合わせるのが最善のケースです。ただし、ここのアルゴリズムには最初の改行文字のみが必要です。
Ole の 2 つの部分で構成される Perl スクリプトは、sysread()
最初の改行文字検索から最大のチャンク読み取りに切り替える方法を示しています。
通常どおりに戻るときは、tail
最後のブロックを読み、改行を計算します。そこから2番目から最後のブロックまで印刷または読み取ります。
答え4
ぜひ使ってみたいのですが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