5

テキスト ファイルには、行で区切られた約 2,500 万の整数があります。私の最初のタスクは、これらの整数を取得して並べ替えることです。私は実際に整数を読み取って配列に入れることに成功しました(ソート関数はソートされていない配列を引数として取るため)。ただし、このファイルからの整数の読み取りは、非常に時間がかかり、コストのかかるプロセスです。これを行うための安価で効率的な方法を得るために、他の多くのソリューションを検索しましたが、そのようなサイズに取り組むソリューションを見つけることができませんでした. したがって、巨大な (約 260MB) テキスト ファイルから整数を読み取るにはどうすればよいでしょうか。また、同じ問題の行数を効率的に取得するにはどうすればよいですか。

ifstream myFile("input.txt");

int currentNumber;
int nItems = 25000000;
int *arr = (int*) malloc(nItems*sizeof(*arr));
int i = 0;
while (myFile >> currentNumber)
{
    arr[i++] = currentNumber;
}

これは、テキスト ファイルから整数を取得する方法です。それほど複雑ではありません。行数は決まっていると思っていた(実際は決まっている)

ちなみに、もちろん遅すぎません。2.2GHz i7プロセッサ搭載のOS Xで約9秒で読み込み完了。しかし、私はそれがもっと良くなる可能性があると感じています。

4

8 に答える 8

8

ほとんどの場合、これを最適化しても効果はほとんどありません。私のマシンでは、大きなファイルの読み取りを制限する要因はディスク転送速度です。はい、読み取り速度を改善することで少し改善できますが、ほとんどの場合、それほど多くは得られません.

以前のテストで [それで答えが見つかるかどうかを確認します - 「SO の実験コード」ディレクトリにソースが見つかりませんでした] を使用してファイルをロードするのが最も速い方法であることがわかりましたmmap。ただし、を使用するよりもわずかに高速ですifstream

編集:いくつかの異なる方法でファイルを読み取るための自家製のベンチマーク。 ファイルの読み取り中の getline とファイル全体の読み取りと、改行文字に基づく分割

いつものように、ベンチマークはベンチマークが測定するものを測定し、環境またはコードの記述方法の小さな変更が大きな違いを生むことがあります。

編集:「ファイルから数値を読み取り、ベクターに格納する」のいくつかの実装を次に示します。

#include <iostream>
#include <fstream>
#include <vector>
#include <sys/time.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>


using namespace std;

const char *file_name = "lots_of_numbers.txt";

void func1()
{
    vector<int> v;
    int num;
    ifstream fin(file_name);
    while( fin >> num )
    {
    v.push_back(num);
    }
    cout << "Number of values read " << v.size() << endl;
}


void func2()
{
    vector<int> v;
    v.reserve(42336000);
    int num;

    ifstream fin(file_name);
    while( fin >> num )
    {
    v.push_back(num);
    }
    cout << "Number of values read " << v.size() << endl;
}

void func3()
{
    int *v = new int[42336000];
    int num;

    ifstream fin(file_name);
    int i = 0;
    while( fin >> num )
    {
    v[i++] = num;
    }
    cout << "Number of values read " << i << endl;
    delete [] v;
}


void func4()
{
    int *v = new int[42336000];
    FILE *f = fopen(file_name, "r");
    int num;
    int i = 0;
    while(fscanf(f, "%d", &num) == 1)
    {
    v[i++] = num;
    }
    cout << "Number of values read " << i << endl;
    fclose(f);
    delete [] v;
}    

void func5()
{
    int *v = new int[42336000];
    int num = 0;

    ifstream fin(file_name);
    char buffer[8192];
    int i = 0;
    int bytes = 0;
    char *p;
    int hasnum = 0;
    int eof = 0;
    while(!eof)
    {
    fin.read(buffer, sizeof(buffer));
    p = buffer;
    bytes = 8192;
    while(bytes > 0)
    {
        if (*p == 26)   // End of file marker...
        {
        eof = 1;
        break;
        }
        if (*p == '\n' || *p == ' ')
        {
        if (hasnum)
            v[i++] = num;
        num = 0;
        p++;
        bytes--;
        hasnum = 0;
        }
        else if (*p >= '0' &&  *p <= '9')
        {
        hasnum = 1;
        num *= 10;
        num += *p-'0';
        p++;
        bytes--;
        }
        else 
        {
        cout << "Error..." << endl;
        exit(1);
        }
    }
    memset(buffer, 26, sizeof(buffer));  // To detect end of files. 
    }
    cout << "Number of values read " << i << endl;
    delete [] v;
}

void func6()
{
    int *v = new int[42336000];
    int num = 0;

    FILE *f = fopen(file_name, "r");
    char buffer[8192];
    int i = 0;
    int bytes = 0;
    char *p;
    int hasnum = 0;
    int eof = 0;
    while(!eof)
    {
    fread(buffer, 1, sizeof(buffer), f);
    p = buffer;
    bytes = 8192;
    while(bytes > 0)
    {
        if (*p == 26)   // End of file marker...
        {
        eof = 1;
        break;
        }
        if (*p == '\n' || *p == ' ')
        {
        if (hasnum)
            v[i++] = num;
        num = 0;
        p++;
        bytes--;
        hasnum = 0;
        }
        else if (*p >= '0' &&  *p <= '9')
        {
        hasnum = 1;
        num *= 10;
        num += *p-'0';
        p++;
        bytes--;
        }
        else 
        {
        cout << "Error..." << endl;
        exit(1);
        }
    }
    memset(buffer, 26, sizeof(buffer));  // To detect end of files. 
    }
    fclose(f);
    cout << "Number of values read " << i << endl;
    delete [] v;
}


void func7()
{
    int *v = new int[42336000];
    int num = 0;

    FILE *f = fopen(file_name, "r");
    int ch;
    int i = 0;
    int hasnum = 0;
    while((ch = fgetc(f)) != EOF)
    {
    if (ch == '\n' || ch == ' ')
    {
        if (hasnum)
        v[i++] = num;
        num = 0;
        hasnum = 0;
    }
    else if (ch >= '0' &&  ch <= '9')
    {
        hasnum = 1;
        num *= 10;
        num += ch-'0';
    }
    else 
    {
        cout << "Error..." << endl;
        exit(1);
    }
    }
    fclose(f);
    cout << "Number of values read " << i << endl;
    delete [] v;
}


void func8()
{
    int *v = new int[42336000];
    int num = 0;

    int f = open(file_name, O_RDONLY);

    off_t size = lseek(f, 0, SEEK_END);
    char *buffer = (char *)mmap(NULL, size, PROT_READ, MAP_PRIVATE, f, 0);

    int i = 0;
    int hasnum = 0;
    int bytes = size;
    char *p = buffer;
    while(bytes > 0)
    {
    if (*p == '\n' || *p == ' ')
    {
        if (hasnum)
        v[i++] = num;
        num = 0;
        p++;
        bytes--;
        hasnum = 0;
    }
    else if (*p >= '0' &&  *p <= '9')
    {
        hasnum = 1;
        num *= 10;
        num += *p-'0';
        p++;
        bytes--;
    }
    else 
    {
        cout << "Error..." << endl;
        exit(1);
    }
    }
    close(f);
    munmap(buffer, size);
    cout << "Number of values read " << i << endl;
    delete [] v;
}






struct bm
{
    void (*f)();
    const char *name;
};

#define BM(f) { f, #f }

bm b[] = 
{
    BM(func1),
    BM(func2),
    BM(func3),
    BM(func4),
    BM(func5),
    BM(func6),
    BM(func7),
    BM(func8),
};


double time_to_double(timeval *t)
{
    return (t->tv_sec + (t->tv_usec/1000000.0)) * 1000.0;
}

double time_diff(timeval *t1, timeval *t2)
{
    return time_to_double(t2) - time_to_double(t1);
}



int main()
{
    for(int i = 0; i < sizeof(b) / sizeof(b[0]); i++)
    {
    timeval t1, t2;
    gettimeofday(&t1, NULL);
    b[i].f();
    gettimeofday(&t2, NULL);
    cout << b[i].name << ": " << time_diff(&t1, &t2) << "ms" << endl;
    }
    for(int i = sizeof(b) / sizeof(b[0])-1; i >= 0; i--)
    {
    timeval t1, t2;
    gettimeofday(&t1, NULL);
    b[i].f();
    gettimeofday(&t2, NULL);
    cout << b[i].name << ": " << time_diff(&t1, &t2) << "ms" << endl;
    }
}

結果 (ファイル キャッシュの利点を回避するために、2 回連続して順方向と逆方向に実行):

Number of values read 42336000
func1: 6068.53ms
Number of values read 42336000
func2: 6421.47ms
Number of values read 42336000
func3: 5756.63ms
Number of values read 42336000
func4: 6947.56ms
Number of values read 42336000
func5: 941.081ms
Number of values read 42336000
func6: 962.831ms
Number of values read 42336000
func7: 2572.4ms
Number of values read 42336000
func8: 816.59ms
Number of values read 42336000
func8: 815.528ms
Number of values read 42336000
func7: 2578.6ms
Number of values read 42336000
func6: 948.185ms
Number of values read 42336000
func5: 932.139ms
Number of values read 42336000
func4: 6988.8ms
Number of values read 42336000
func3: 5750.03ms
Number of values read 42336000
func2: 6380.36ms
Number of values read 42336000
func1: 6050.45ms

要約すると、誰かがコメントで指摘したように、整数の実際の解析は全体のかなりの部分を占めるため、ファイルの読み取りは最初に考えたほど重要ではありません。ファイルを読み取る非常に単純な方法でさえ(整数fgetc()のビートを使用します。ifstream operator>>

ご覧のとおり、 を使用mmapしてファイルをロードすると、 を介してファイルを読み取るよりもわずかに高速ですが、わずかに高速ですfstream

于 2013-02-27T15:41:34.937 に答える
3

外部ソートを使用して、すべての値をメモリにロードせずにファイル内の値をソートできます。並べ替えの速度はハードドライブの機能によって制限されますが、非常に大きなファイルをいじることができます。これが実装です。

于 2013-02-27T16:11:40.870 に答える
0

私はこのようにします:

#include <fstream>
#include <iostream>
#include <string>

using namespace std;

int main() {

    fstream file;
    string line;
    int intValue;
    int lineCount = 0;
    try {
        file.open("myFile.txt", ios_base::in); // Open to read
        while(getline(file, line)) {
            lineCount++;
            try {
                intValue = stoi(line);
                // Do something with your value
                cout << "Value for line " << lineCount << " : " << intValue << endl;

            } catch (const exception& e) {
                cerr << "Failed to convert line " << lineCount << " to an int : " << e.what() << endl;
            }
        }
    } catch (const exception& e) {
        cerr << e.what() << endl;
        if (file.is_open()) {
            file.close();
        }
    }

    cout << "Line count : " << lineCount << endl;

    system("PAUSE");
}
于 2013-02-27T15:53:10.507 に答える
0

考えられる解決策の 1 つは、大きなファイルを小さなチャンクに分割することです。各チャンクを個別に並べ替えてから、並べ替えられたすべてのチャンクを 1 つずつマージします。

編集:どうやらこれは確立された方法です。http://en.wikipedia.org/wiki/External_sortingの「外部マージ ソート」を参照してください。

于 2013-02-27T15:42:19.470 に答える
0

260MBはそれほど大きくありません。全体をメモリにロードして、それを解析できるはずです。ネストされたループを使用して、行末間の整数を読み取り、通常の関数を使用して変換できます。開始する前に、整数の配列に十分なメモリを事前に割り当てようとします。

ああ、おおざっぱな古い C スタイルのファイル アクセス関数が、このような場合のより高速なオプションであることに気付くかもしれません。

于 2013-02-27T15:42:46.520 に答える
0

値をどのように読んでいるのかは言わないので、言うのは難しいです。それでも、実際には 2 つの解決策しかありません。

anInt andfscanf( someFd, "%d", &anInt )` 論理的には、これらは同様のパフォーマンスを持つはずですが、実装は異なります。両方を試して測定する価値があるかもしれません。

もう一つ確認しておきたいのは、保管方法です。約 2500 万を持っていることがわかっている場合は、それらを読む前reserveに 3000 万を実行すると、std::vectorおそらく役立つでしょう。vectorを使用するよりも、3,000 万の要素を使用して を構築し、最後が見えたらそれをトリミングする方が安価な場合もありますpush_back

最後に、 を書き込んでimmapstreambufそれを入力に使用しmmap、マップされたメモリから直接読み取ることを検討してください。または、手動で呼び出して繰り返し処理することもできます strtol(ただし、それはより多くの作業です)。すべてのストリーミング ソリューションは、おそらく最終的strtolに 、または同様のものを呼び出すことになりますが、最初に呼び出しを回避する重要な作業を行います。

編集:

FWIW、自宅のマシン (Linux を実行しているかなり最近の LeNova) でいくつかの非常に簡単なテストを行ったところ、驚くべき結果が得られました。

  • 参考までに、最適化を試みずにstd::cin >> tmpandを使用して、簡単で素朴な実装を 行いました。v.push_back( tmp );私のシステムでは、これは 10 秒弱で実行されました。

  • reserveベクトルでの使用や、最初にサイズ 25000000 のベクトルを作成するなどの単純な最適化では、大きな変化はありませんでした。時間は依然として 9 秒を超えていました。

  • 非常に単純な を使用するmmapstreambufと、時間が約 3 秒に短縮されましたreserve

  • を使用するfscanfと、時間が 3 秒弱に短縮されました。の Linux 実装FILE*も使用し ていると思われますmmap(使用std::filebufしていません)。

  • 最後に、 a を使用しmmapbuffer、 two で反復しchar*、 stdtol を使用して変換すると、時間が 1 秒未満に短縮されました。

これらのテストは非常に迅速に行われ (すべてを記述して実行するのに 1 時間もかからず)、厳格とはほど遠い (もちろん、他の環境については何も教えてくれません) が、その違いには驚かされました。私はそれほど違いを期待していませんでした。

于 2013-02-27T15:49:40.253 に答える
0

Qt を使用すると、非常に簡単になります。

QFile file("h:/1.txt");
file.open(QIODevice::ReadOnly);
QDataStream in(&file);

QVector<int> ints;
ints.reserve(25000000);

while (!in.atEnd()) {
    int integer;
    qint8 line; 
    in >> integer >> line; // read an int into integer, a char into line
    ints.append(integer); // append the integer to the vector
}

最後に、ints簡単にソートできる QVector があります。ファイルが適切にフォーマットされていれば、行数はベクトルのサイズと同じです。

私のマシン、i7 3770k @4.2 Ghz では、2500 万の int を読み取ってベクトルに入れるのに約 490 ミリ秒かかります。SSD ではなく、通常の機械式 HDD からの読み取り。

ファイル全体をメモリにバッファリングしても、それほど効果はなく、時間は 420 ミリ秒に短縮されました。

于 2013-02-27T15:58:31.673 に答える
0

行ごとに読み取るのではなく、整数のブロックを読み取ってそれらのブロックを解析してみてください。

于 2013-02-27T15:38:29.670 に答える