「wc -l」よりも高速なものが必要


12

1GBのような非常に大きなファイルのwc -l場合、遅くなることがあります。特定のファイルの改行の数を計算するより速い方法はありますか?


25
より高速なディスクを購入しますか?入力の各バイトを検査する必要があることを0x0A考えると、I / Oは間違いなくボトルネックです。
2016年

2
wcオーバーヘッドが多すぎると思われる場合は、独自に実装してみてくださいforeach byte in file: if byte == '\n': linecount++。Cまたはアセンブラーで実装されている場合、おそらく優先度が最も高いRTOSのカーネル空間を除いて(またはそのために割り込みを使用する場合を除いて)、それ以上速くなるとは思いません(システムで他に何もできない)。 ..大丈夫、私は脱線します;
マーフィー

3
そして、スケールの感触を得るために、私はtime wc -l some_movie.aviキャッシュされていないファイルをすばやく試してみました5172672 some_movie.avi -- real 0m57.768s -- user 0m0.255s -- sys 0m0.863s。これは基本的に@thrigが正しいことを証明します。この場合、I / Oはパフォーマンスを粉砕します。
マーフィー

10
それがディスクIOボトルネックであることを示す最良の方法は、time wc -l some_large_file_smaller_than_cache連続して2回実行し、2番目の操作がどれだけ速いtime wc -l some_large_file_larger_than_cacheかを確認してから、実行間で時間が変化しないことを確認することです。ここで約280MBのファイルの場合、時間は1.7秒から0.2秒になりますが、2GBのファイルの場合は両方とも14秒です。
EightBitTony 2016年

1
あなたにとってどれほど遅いのが遅すぎるのですか?何/usr/bin/time wc -l <file>と言うの?あなたのハードウェアは何ですか?コマンドを繰り返し実行する方が速いですか?本当にもっと情報が必要です;)
marcelm

回答:


21

あなたはCで書くことを試みることができます

#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(){
  char buf[BUFSIZ];
  int nread;
  size_t nfound=0;
  while((nread=read(0, buf, BUFSIZ))>0){
    char const* p;
    for(p=buf; p=memchr(p,'\n',nread-(p-buf)); nfound++,p++) {;}
  }
  if(nread<0) { perror("Error"); return 1; }
  printf("%lu\n", nfound);
  return 0;
}

たとえばに保存し、たとえばでwcl.cコンパイルしてgcc wcl.c -O2 -o wcl、で実行します

<yourFile ./wcl

これにより、システムの1GBファイルに約370ms(繰り返し実行)で改行が散在していることがわかります。(バッファーサイズを大きくすると、時間はわずかに長くなります。これは予想されることです-BUFSIZは最適に近いはずです)。これは、私が取得している約380msに非常に匹敵しwc -lます。

Mmapingは約280msのより良い時間を与えますが、もちろん実際のファイルに制限されるという制限があります(FIFOSなし、端末入力なしなど):

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
  struct stat sbuf;
  if(fstat(0, &sbuf)<0){ perror("Can't stat stdin"); return 1; }

  char* buf = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, 0/*stdin*/, 0/*offset*/);
  if(buf == MAP_FAILED){ perror("Mmap error"); return 1; } 

  size_t nread = sbuf.st_size, nfound=0;
  char const* p;
  for(p=buf; p=memchr(p,'\n',nread-(p-buf)); nfound++,p++) {;}

  printf("%lu\n", nfound);
  return 0;
}

私はテストファイルを作成しました:

 $ dd if=/dev/zero of=file bs=1M count=1042 

そして、いくつかのテスト改行を追加しました:

 $ echo >> 1GB 

16進エディタ。


mmapの結果TBHに驚きました。以前はmmapingが読み取り/書き込みよりも高速であると考えていましたが、それとは反対のLinuxベンチマークがいくつかありました。この場合は非常に本当のようです。
PSkocik 2016年

4
mmapはLinuxで非常に優れた結果を得るでしょう。これは、最近の巨大なページにマッピングされ、TLBミスが非常に少ないためですwwwwww。
jthill 2016年

ファイルの異なる部分を別々のスレッドで(たとえば、OpenMP forループを使用して)読み取ると、1つのスレッドが入力を待っている間にストールする可能性があるため、いくつかの利点があります。しかし、その一方で、I / Oスケジューリングを妨げる可能性があるため、私がお勧めできるのは、それを試して測定することだけです。
Toby Speight

read()バージョンは、先読みから利益を得ることができます。
Barmar

1
@TobySpeightええ、マルチスレッドはそれをスピードアップするかもしれません。また、2 ^ 16のルックアップテーブルを介して一度に2バイトをスキャンして見ることで、前回私が遊んだときの速度がかなり向上しました。
PSkocik

18

への呼び出し回数を減らすことで、@ pskocikによって提案されたソリューションを改善できますread。1Gbファイルからチャンクを読み取るための呼び出しはたくさんありBUFSIZます。これを行う通常の方法は、バッファサイズを増やすことです。

  • 面白さのために、バッファサイズを10倍または100倍に増やしてみてください。私のDebian 7ではBUFSIZ8192です。元のプログラムでは、これは12万回の読み取り操作です。おそらく、1Mbの入力バッファーで100倍に削減できます。
  • より最適な方法として、アプリケーションはファイルと同じ大きさのバッファを割り当てる場合があり、単一の読み取り操作が必要になります。これは「小さな」ファイルに対しては十分に機能します(ただし、一部のリーダーのマシンには1 GBを超えるものがあります)。
  • 最後に、そのように割り当てを処理するメモリマップI / Oを試すことができます。

さまざまなアプローチをベンチマークするとき、一部のシステム(Linuxなど)は、マシンのほとんどの未使用メモリをディスクキャッシュとして使用することに注意してください。しばらく前(ほぼ20年前、悪質なFAQで言及されていました)に、テキストエディターでメモリ不足の状態を処理するために開発した(あまり良くない)ページングアルゴリズムの予想外に良い結果に戸惑いました。プログラムはファイルの読み取りに使用されるメモリバッファーから動作していたため高速で実行され、ファイルが再読み取りまたは書き込みされた場合のみ速度に違いがあると説明されました。

同じことが当てはまりますmmap(別のケースではまだFAQに組み込むためにto-doリストにありますが、開発者はディスクキャッシュが改善の実際の理由であるシナリオで非常に良い結果を報告しました)。ベンチマークの開発には時間がかかり、パフォーマンスが良い(または悪い)理由を分析することに注意が必要です。

参考文献:


2
特定のしきい値を超えるバッファサイズの影響を過大評価しています。通常、バッファサイズを4KBを超えるように増やしてもあまり効果はありません。また、バッファがL1キャッシュから押し出される可能性があるため、実際には有害な場合があります。私のマシンではdd、1MBのバッファを使用してでテストすると、8KB より遅くなります。wcの8KBのデフォルト値は実際にはかなり適切に選択されており、幅広いシステムに最適です。
marcelm 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.