11

私のタスクは非常に単純です。Linux 上の C++ で大きなファイルを読み取って解析します。次の 2 つの方法があります。

  1. バイトごとに解析します。

    while(/*...*/) {
            ... = fgetc(...);
            /* do something with the char */
    }
    
  2. バッファごとに解析します。

    while(/*...*/) {
            char buffer[SOME_LARGE_NUMBER];
            fread(buffer, SOME_LARGE_NUMBER, 1, ...);
            /* parse the buffer */
    }
    

これで、バイトごとの解析が簡単になりました(バッファがどれだけいっぱいかなどのチェックはありません)。しかし、大きな断片を読む方が効率的だと聞きました。

哲学とは何ですか?カーネルのタスクを「最適な」バッファリングしているので、呼び出したときにすでにバッファリングされていますfgetc()か? それとも、最高の効率を得るためにそれを処理することをお勧めしますか?

また、すべての哲学とは別に、ここでの Linux の現実は何ですか?

4

5 に答える 5

11

のパフォーマンスや基礎となるバッファリングに関係なく、fgetc()必要な 1 バイトごとに関数を呼び出すことと、適切なサイズのバッファーを反復処理することは、カーネルが支援できないオーバーヘッドです。

私は自分のローカル システム (明らかに YMMV) に対していくつかの迅速で汚いタイミングを実行しました。

~200k ファイルを選択し、各バイトを合計しました。fgetc()これを 20000 回行い、1000 サイクルごとに を使用した読み取りと を使用した読み取りを交互に行いfread()ました。1000 サイクルごとに 1 つの塊として計測しました。最適化をオンにして、リリース ビルドをコンパイルしました。

fgetc()ループ バリアントは、一貫してループよりも45 倍遅くなりましたfread()

コメントでプロンプトを表示した後getc()、標準入出力バッファーも比較し、変更しました。パフォーマンスに目立った変化はありませんでした。

于 2012-12-22T13:15:31.167 に答える
3

stdioバッファーはカーネルの一部ではありません。ユーザー空間の一部です。

ただし、 setbufを使用してそのバッファーのサイズに影響を与えることができます。そのバッファが十分に満たされていない場合、stdioライブラリはreadシステム関数を発行してバッファを埋めます。

したがって、fgetcを使用しても、カーネルとユーザーを切り替えるという条件を無視してもかまいません。

于 2012-12-22T13:23:15.983 に答える
1

fgetc の遅さの原因は、関数呼び出しの量ではなく、システム コールの量です。fgetcとして実装されることが多いint fgetc(FILE *fp) { int ch; return (fread(&ch,1,1,fp)==1?ch:-1); }

fread 自体が 64k または 1k をバッファリングする場合でも、システム コールのオーバーヘッドは、たとえば

 int fgetc_buffered(FILE *fp) {
     static int head=0,tail=0; 
     static unsigned char buffer[1024];
     if (head>tail) return buffer[tail++];
     tail=0;head=fread(buffer,1,1024,fp);
     if (head<=0) return -1;
     return buffer[tail++];
 }
于 2012-12-22T15:01:55.520 に答える
1

関係ありません、本当に。SSD からでも、I/O オーバーヘッドはバッファリングに費やされる時間を小さくします。確かに、ミリ秒ではなくマイクロ秒になりましたが、関数呼び出しはナノ秒単位で測定されます。

于 2012-12-22T13:23:26.603 に答える
0

stdioルーチンは、ユーザースペースのバッファリングを行います。getc、fgetc、freadを呼び出すと、stdioユーザースペースバッファーからデータをフェッチします。バッファが空の場合、stdioはカーネル読み取り呼び出しを使用してより多くのデータを取得します。

ファイルシステムを設計する人々は、ディスクアクセス(主にシーク)が非常に高価であることを知っています。したがって、stdioが512バイトのブロックサイズを使用している場合でも、ファイルシステムは4 KBのブロックサイズを使用する可能性があり、カーネルは一度に4KBのファイルを読み取ります。

通常、カーネルは読み取りを取得した後、ディスク/ネットワーク要求を開始します。ディスクの場合、ファイルを順番に読み取っていることがわかると、先読みが開始され(ブロックを要求する前にブロックを取得)、データをより迅速に利用できるようになります。

また、カーネルはファイルをメモリにキャッシュします。したがって、読み取っているファイルがメモリに収まる場合、プログラムを1回実行した後、参照している他のファイルをキャッシュする方がよいとカーネルが判断するまで、ファイルはメモリに残ります。

mmapを使用しても、先読みされたカーネルのメリットは得られません。

于 2012-12-22T14:27:33.450 に答える