さて、コメントから、あなたはできるだけ速い解決策を望んでいるようです。
その要件に近い何かを達成するために私がすることは次のとおりです。
おそらくメモリプールアロケータを使って文字列を割り当てることはできますが、STLは私の強みではないので、手作業で行います。これは必ずしもC++で行う方法ではないことに注意してください。そのため、C++の頭が少ししわが寄る可能性があります。少し専門的なものが必要な場合は、これを行う必要がある場合があります。
したがって、データファイルは約10GBです...1つのブロックに割り当てるのはお勧めできません。ほとんどの場合、OSは拒否します。しかし、それをかなり大きなブロックの束に分割することは問題ありません。ここにマジックナンバーがあるかもしれませんが、約64MBとしましょう。ページングの専門家である人々はここにコメントできますか?正確なページサイズの倍数より少し小さいものを使用するのが良いことを一度読んだことを覚えています(理由は思い出せませんが)。それでは、数kBを取り除いてみましょう。
const size_t blockSize = 64 * 1048576 - 4096;
さて、あなたの記憶を追跡するための構造はどうですか?それらをすべて一緒に投げることができるように、それをリストにすることもできます。
struct Block {
SBlock *next;
char *data; // Some APIs use data[1] so you can use the first element, but
// that's a hack that might not work on all compilers.
} SBlock;
そうです、ブロックを割り当てる必要があります。メモリの大きなチャンクを割り当て、最初の少しを使用して情報を格納します。data
メモリを調整する必要がある場合は、ポインタを変更できることに注意してください。
SBlock * NewBlock( size_t blockSize, SBlock *prev = NULL )
{
SBlock * b = (SBlock*)new char [sizeof(SBlock) + blockSize];
if( prev != NULL ) prev->next = b;
b->next = NULL;
b->data = (char*)(blocks + 1); // First char following struct
b->length = blockSize;
return b;
}
今、あなたは読むつもりです...
FILE *infile = fopen( "mydata.csv", "rb" ); // Told you C++ers would hate me
SBlock *blocks = NULL;
SBlock *block = NULL;
size_t spilloverBytes = 0;
while( !feof(infile) ) {
// Allocate new block. If there was spillover, a new block will already
// be waiting so don't do anything.
if( spilloverBytes == 0 ) block = NewBlock( blockSize, block );
// Set list head.
if( blocks == NULL ) blocks = block;
// Read a block of data
size_t nBytesReq = block->length - spilloverBytes;
char* front = block->data + spilloverBytes;
size_t nBytes = fread( (void*)front, 1, nBytesReq, infile );
if( nBytes == 0 ) {
block->length = spilloverBytes;
break;
}
// Search backwards for a newline and treat all characters after that newline
// as spillover -- they will be copied into the next block.
char *back = front + nBytes - 1;
while( back > front && *back != '\n' ) back--;
back++;
spilloverBytes = block->length - (back - front);
block->length = back - block->data;
// Transfer that data to a new block and resize current block.
if( spilloverBytes > 0 ) {
block = NewBlock( blockSize, block );
memcpy( block->data, back, spilloverBytes );
}
}
fclose(infile);
さて、そのようなもの。あなたはジストを取得します。この時点で、を複数回呼び出すよりもかなり速くファイルを読み取った可能性があることに注意してくださいstd::getline
。キャッシュを無効にできれば、さらに高速になります。Windowsでは、CreateFile
APIを使用して、実際の高速読み取り用に微調整できます。したがって、データブロックを(ディスクセクターサイズに)整列させる可能性についての私の以前のコメント。Linuxやその他のOSについてはよくわかりません。
したがって、これはファイル全体をメモリに丸呑みする一種の複雑な方法ですが、アクセス可能で適度に柔軟性があるほど単純です。うまくいけば、私はあまり多くの間違いをしませんでした。ここで、ブロックのリストを調べて、インデックスの作成を開始します。
ここではあまり詳しく説明しませんが、一般的な考え方はこれです。適切な場所でNULL値をブリットし、各トークンがどこから始まったかを追跡することにより、インプレースでトークン化します。
SBlock *block = blocks;
while( block ) {
char *c = block->data;
char *back = c + block->length;
char *token = NULL;
// Find first token
while( c != back ) {
if( c != '"' && c != '*' ** c != '\n' ) break;
c++;
}
token = c;
// Tokenise entire block
while( c != back ) {
switch( *c ) {
case '"':
// For speed, we assume all closing quotes have opening quotes. If
// we have closing quote without opening quote, this won't be correct
if( token != c) {
*c = 0;
token++;
}
break;
case '*':
// Record separator
*c = 0;
tokens.push_back(token); // You can do better than this...
token = c + 1;
break;
case '\n':
// Record and line separator
*c = 0;
tokens.push_back(token); // You can do better than this...
lines.push_back(tokens); // ... and WAY better than this...
tokens.clear(); // Arrrgh!
token = c + 1;
break;
}
c++;
}
// Next block.
block = block->next;
}
最後に、上記のベクトルのような呼び出しが表示されます。さて、もしあなたがあなたのベクトルをメモリプールすることができれば、それは素晴らしくて簡単です。しかし、繰り返しになりますが、メモリを直接操作する方がはるかに直感的であるため、私は決してそれを行いません。ファイルチャンクで行ったのと同様のことを行うことができますが、配列(またはリスト)用のメモリを作成します。このメモリ領域にすべてのトークン(8バイトのポインタ)を追加し、必要に応じて新しいメモリチャンクを追加します。
これらのトークン配列の1つに含まれるアイテムの数を追跡する小さなヘッダーを作成することもできます。重要なのは、追加費用なしで後で計算できるものを一度計算することではありません(つまり、配列サイズ-最後の要素を追加した後でのみ計算する必要があります)。
線でも同じことをします。必要なのは、トークンチャンク内の関連部分へのポインターだけです(配列のインデックス付けが必要な場合は、行が新しいチャンクに食い込んだ場合にスピルオーバーを実行する必要があります)。
最終的には、トークンの配列を指す行の配列になります。これは、ファイルから丸呑みしたメモリを直接指します。メモリの浪費は少しありますが、おそらく過度ではありません。これは、コードを高速化するために支払う価格です。
いくつかの簡単なクラスですべてを美しくまとめることができると確信していますが、ここで生で提供しました。大量のSTLコンテナーをメモリー・プールにしたとしても、それらのアロケーターとコンテナー自体のオーバーヘッドにより、私が提供したものよりも遅くなると思います。本当に長い答えでごめんなさい。私はこのようなものを楽しんでいると思います。楽しんでください、そしてこれが役立つことを願っています。