paste
は素晴らしいツールですが、実行時にサーバー上で約50MB / sで非常に遅いです。
paste -d, file1 file2 ... file10000 | pv >/dev/null
paste
によると、100%CPUを使用するため、top
ディスク速度の低下などの要因によって制限されません。
ソースコードを見ると、次のものが使用されている可能性がありますgetc
。
while (chr != EOF)
{
sometodo = true;
if (chr == line_delim)
break;
xputchar (chr);
chr = getc (fileptr[i]);
err = errno;
}
同じことをしますが、より速く実行できる他のツールはありますか?一度に4k-64kチャンクを読むことができますか?一度に1バイトずつ表示する代わりに、ベクトル命令を使用して並行して改行文字を見つけることは可能ですか?おそらく使用するかawk
似ていますか?
入力ファイルはUTF8で、大きすぎてRAMに収まらないので読み込みすべてメモリに入るのはオプションではありません。
編集する:
Thanasispでは、ジョブを並列に実行することをお勧めします。これはスループットをわずかに向上させますが、まだ純粋なものよりもはるかに遅くなりますpv
。
# Baseline
$ pv file* | head -c 10G >/dev/null
10.0GiB 0:00:11 [ 897MiB/s] [> ] 3%
# Paste all files at once
$ paste -d, file* | pv | head -c 1G >/dev/null
1.00GiB 0:00:21 [48.5MiB/s] [ <=> ]
# Paste 11% at a time in parallel, and finally paste these
$ paste -d, <(paste -d, file1*) <(paste -d, file2*) <(paste -d, file3*) \
<(paste -d, file4*) <(paste -d, file5*) <(paste -d, file6*) \
<(paste -d, file7*) <(paste -d, file8*) <(paste -d, file9*) |
pv | head -c 1G > /dev/null
1.00GiB 0:00:14 [69.2MiB/s] [ <=> ]
top
それでも、外部ソースがpaste
ボトルネックを引き起こしているようです。
バッファを増やすと違いがあるかどうかをテストしました。
$ stdbuf -i8191 -o8191 paste -d, <(paste -d, file1?) <(paste -d, file2?) <(paste -d, file3?) <(paste -d, file4?) <(paste -d, file5?) <(paste -d, file6?) <(paste -d, file7?) <(paste -d, file8?) <(paste -d, file9?) | pv | head -c 1G > /dev/null
1.00GiB 0:00:12 [80.8MiB/s] [ <=> ]
これによりスループットが 10% 増加します。バッファを増やしても何も改善されませんでした。これはハードウェアに関連している可能性があります(つまり、レベル1のCPUキャッシュのサイズが原因である可能性があります)。
テストは、ディスクサブシステムに関連する制限を避けるためにRAMディスクで実行されます。
ベストアンサー1
さまざまな選択肢とシナリオを使用した追加テスト
(編集:コンパイルされたバージョンのカットオフ値を追加)
簡単に言うと:
- はい、coreutils
paste
が良いですcat
paste
特に、大量の短い行では、より高速なcoreutilsの代わりに簡単に使用できる代替手段はないようです。paste
スループットは、ライン長、ライン数、ファイル数のさまざまな組み合わせで驚くほど安定しています。- より長い路線の場合、より速い代替案が以下に提供される。
詳細:
多くのシナリオをテストしました。スループット測定はpv
もともと投稿と同様に行われた。
比較オプション:
cat
(基準としてGNU coreutils 8.25から)paste
(また、GNU coreutils 8.25で)- Pythonスクリプト~から上記で回答しました
- 代替Pythonスクリプト(行フラグメントを収集するためのリストの理解を通常のループに置き換えます)
- プロジェクト(4に似ていますが、実行可能ファイルとしてコンパイルされています)
ファイル/行番号の組み合わせ:
# | リスト | ワイヤー |
---|---|---|
1 | 200,000 | 1,000 |
2 | 20,000 | 10,000 |
サム | 2,000 | 100,000 |
4 | 200 | 1,000,000 |
5 | 20 | 10,000,000 |
6 | 2 | 100,000,000 |
各テストの総データ容量は1.3GBと同じであった。各列は6桁の数字(000'001〜200'000など)で構成されています。上記の組み合わせは、1、10、100、1'000、10'000の同じサイズのファイルにできるだけ多く配布されます。
生成されたファイルは次のとおりです。yes {000001..200000} | head -1000 > 1
貼り付けは次のように行われます。for i in cat paste ./paste ./paste2 ./paste3; do $i {00001..1000} | pv > /dev/null; done
ただし、貼り付けたファイルは実際にはすべて同じソースファイルへのリンクなので、すべてのデータはとにかくキャッシュに存在する必要があります(貼り付けの直前に作成され、最初に読み込まれます;cat
システムメモリは128 GB、キャッシュサイズ34 GB)
あらかじめ作成されたファイルから読み込んでパイプに接続する代わりに、データをすぐに生成する別のセットが実行されましたpaste
(下の表示、ファイル数= 0)。
最後のコマンドセットの場合、次のようになります。for i in cat paste ./paste ./paste2 ./paste3; do $i <(yes {000001..200000} | head -1000) | pv > /dev/null; done
発見する:
paste
数十倍遅い速度cat
paste
スループットは、さまざまな線幅と関連ファイルの数にわたって非常に一定です(約300 MB / s)。- 平均入力ファイル行の長さが特定の制限(私のテストコンピュータでは約1400文字)を超えると、自家製のPythonの代替案がいくつかの利点を示すことがあります。
- Pythonスクリプトと比較してコンパイルされたnimバージョンは、スループットが約2倍です。これに対して、
paste
入力ファイルの損益分岐点は約500文字です。この値は、入力ファイルの数が増えるにつれて減少し、入力ファイルが10個以上含まれると、入力ファイルの行ごとに約150文字まで減ります。 - Pythonとnimの両方のバージョンは、多くの短い行の処理オーバーヘッドによって困難に直面しています(疑わしい原因:使用されている2つのstdlib関数で行の終わりを検出し、それをプラットフォーム固有の端に変換しようとしました)。ただし、coreutilsは
paste
影響を受けません。 cat
同時動的データ生成プロセスは、行が長いnimバージョンの制限要因であり、処理速度にある程度影響を与えるようです。- ある時点では、多数のオープンファイルハンドルがcoreutilsに悪影響を及ぼすように見えます
paste
。 (ただ推測です。たぶんこれは並列バージョンにも影響しますか?)
結論(少なくともテストマシンの場合)
- 入力ファイルが狭い場合、特にファイルが長い場合はcoreutils Pasteを使用してください。
- 入力ファイルがかなり広い場合は、選択肢が優先されます(入力ファイルの数に応じて、Pythonバージョンの場合は入力ファイルの行の長さ> 1400文字、nimバージョンの場合は150〜500文字)。
- 通常、Pythonスクリプトの代わりにコンパイルされたnimバージョンを好みます。
- 小さな部分が多すぎる場合は注意してください。この場合、プロセスのデフォルトのソフト制限である1024のオープンファイルはかなり合理的に見えます。
OP状況に対する提案(並列処理)
入力ファイルが狭い場合は、paste
内部操作でcoreutilsを使用し、コンパイルされた代替最も外側のプロセスの場合。すべてのファイルに長い行がある場合は、通常はnimバージョンを使用してください。
洞窟:リンクされたプログラムは一時的なリリースであり、いかなる保証や明示的なエラー処理もせずに「現状のまま」提供されます。さらに、3つの実装すべてで区切り文字がハードコードされています。