現在、ハードディスク上のファイルの各行を処理したいと考えています。ファイル全体をロードしてから改行文字に基づいて分割する方が良いですか (boost を使用)、それとも を使用する方が良いgetline()
ですか? 私の質問はgetline()
、呼び出されたときに単一行を読み取る (複数のハードディスク アクセスが発生する) か、ファイル全体を読み取って行ごとに与えるかです。
6 に答える
getline
read()
Cライブラリの根底のどこかでシステムコールとして呼び出されます。正確に何回呼び出されるか、どのように呼び出されるかは、Cライブラリの設計によって異なります。ただし、最下層のOSは(少なくとも)一度に1つのディスクブロックを読み取り、おそらく少なくとも「ページ」を読み取るため、一度に1行を読み取る場合とファイル全体を読み取る場合に明確な違いはない可能性があります。 "(4KB)、それ以上ではないにしても。
さらに、文字列を読んだ後はほとんど何もしません(たとえば、「grep」のようなものを書いているので、ほとんどの場合、文字列を見つけるためにを読むだけです)、一度に1行を読むオーバーヘッドはほとんどありません。あなたが費やす時間の大部分です。
しかし、「ファイル全体を一度にロードする」には、いくつかの明確な問題があります。
- ファイル全体を読み取るまで、処理を開始しません。
- ファイル全体をメモリに読み込むのに十分なメモリが必要です-ファイルのサイズが数百GBの場合はどうなりますか?あなたのプログラムは失敗しますか?
プロファイリングを使用してコードの実行速度が遅い理由の一部であることを証明した場合を除いて、何かを最適化しようとしないでください。あなたは自分自身のためにより多くの問題を引き起こしているだけです。
編集:それで、私はこれを測定するプログラムを書きました。それは非常に興味深いと思うからです。
そして、結果は間違いなく興味深いものです。比較を公平にするために、それぞれ1297984192バイトの3つの大きなファイルを作成しました(約12の異なるソースファイルを含むディレクトリ内のすべてのソースファイルをコピーし、このファイルを数回コピーして「乗算」します)それは、テストの実行に1.5秒以上かかるまでです。これは、タイミングがランダムな「ネットワークパケットの着信」やその他の外部の影響を受けてタイムアウトにならないようにするために、実行する必要がある時間です。プロセスの)。
また、プロセスごとにシステムとユーザー時間を測定することにしました。
$ ./bigfile
Lines=24812608
Wallclock time for mmap is 1.98 (user:1.83 system: 0.14)
Lines=24812608
Wallclock time for getline is 2.07 (user:1.68 system: 0.389)
Lines=24812608
Wallclock time for readwhole is 2.52 (user:1.79 system: 0.723)
$ ./bigfile
Lines=24812608
Wallclock time for mmap is 1.96 (user:1.83 system: 0.12)
Lines=24812608
Wallclock time for getline is 2.07 (user:1.67 system: 0.392)
Lines=24812608
Wallclock time for readwhole is 2.48 (user:1.76 system: 0.707)
ファイルを読み取るための3つの異なる関数を次に示します(もちろん、時間などを測定するためのコードもありますが、この投稿のサイズを小さくするために、すべてを投稿しないことを選択しました-そして、注文してみましたそれは違いを生んだので、上記の結果はここの関数と同じ順序ではありません)
void func_readwhole(const char *name)
{
string fullname = string("bigfile_") + name;
ifstream f(fullname.c_str());
if (!f)
{
cerr << "could not open file for " << fullname << endl;
exit(1);
}
f.seekg(0, ios::end);
streampos size = f.tellg();
f.seekg(0, ios::beg);
char* buffer = new char[size];
f.read(buffer, size);
if (f.gcount() != size)
{
cerr << "Read failed ...\n";
exit(1);
}
stringstream ss;
ss.rdbuf()->pubsetbuf(buffer, size);
int lines = 0;
string str;
while(getline(ss, str))
{
lines++;
}
f.close();
cout << "Lines=" << lines << endl;
delete [] buffer;
}
void func_getline(const char *name)
{
string fullname = string("bigfile_") + name;
ifstream f(fullname.c_str());
if (!f)
{
cerr << "could not open file for " << fullname << endl;
exit(1);
}
string str;
int lines = 0;
while(getline(f, str))
{
lines++;
}
cout << "Lines=" << lines << endl;
f.close();
}
void func_mmap(const char *name)
{
char *buffer;
string fullname = string("bigfile_") + name;
int f = open(fullname.c_str(), O_RDONLY);
off_t size = lseek(f, 0, SEEK_END);
lseek(f, 0, SEEK_SET);
buffer = (char *)mmap(NULL, size, PROT_READ, MAP_PRIVATE, f, 0);
stringstream ss;
ss.rdbuf()->pubsetbuf(buffer, size);
int lines = 0;
string str;
while(getline(ss, str))
{
lines++;
}
munmap(buffer, size);
cout << "Lines=" << lines << endl;
}
OS はデータのブロック全体を読み取り (ディスクのフォーマット方法に応じて、通常は一度に 4 ~ 8k)、バッファリングの一部を行います。OS に処理を任せて、プログラムにとって意味のある方法でデータを読み取ります。
fstream は適切にバッファリングされます。OS によるハードディスクへの基になるアクセスは、適切にバッファリングされます。ハードディスク自体には妥当なバッファがあります。ファイルを 1 行ずつ読み取れば、ハードディスクへのアクセスが増えることはないでしょう。または、文字ごとに。
したがって、ファイル全体を大きなバッファーにロードしてそのバッファーで作業する理由はありません。それは既にバッファーにあるからです。また、一度に 1 行ずつバッファリングする理由もありません。ifstream で既にバッファリングされている文字列内の何かをバッファリングするためにメモリを割り当てるのはなぜですか? 可能であれば、ストリームで直接作業し、1 つのバッファーから次のバッファーにすべてを 2 回以上投げる必要はありません。可読性をサポートしていない場合、および/またはプロファイラーがディスクアクセスがプログラムの速度を大幅に低下させていることを通知した場合を除きます。
データをメモリに収容できる場合は、すべてのデータをフェッチすることをお勧めします。これは、I / Oを要求するたびに、プログラムが処理を失い、待機Qを入れるためです。
ただし、ファイルサイズが大きい場合は、処理に必要な量のデータを一度に読み取ることをお勧めします。大きな読み取り操作は、小さな読み取り操作よりも完了するのに多くの時間がかかるためです。CPUプロセスの切り替え時間は、このファイル全体の読み取り時間よりもはるかに短くなります。
C++ のイディオムは、ファイルを 1 行ずつ読み取り、ファイルを読み取るときに行ベースのコンテナーを作成することだと思います。ほとんどの場合、iostreams ( getline
) は十分にバッファリングされるため、大きな違いに気付かないでしょう。
ただし、ファイルが非常に大きい場合は、(一度にファイル全体ではなく) ファイルのより大きなチャンクを読み取り、改行が見つかったときに内部で分割することにより、パフォーマンスが向上する場合があります。
具体的にどの方法がどれだけ高速かを知りたい場合は、コードをプロファイリングする必要があります。
ディスク上の小さなファイルの場合は、一度に 1 行ずつ読み取るよりも、ファイル全体を読み取って 1 行ずつ解析する方がおそらく効率的です。これには大量のディスク アクセスが必要です。