2016-12-21 6 views
7

メモリRAMに収まる妥当なサイズのテキストファイルを解析するためのフレームワークを作成しましたが、現在は順調に進んでいます。私は苦情はありませんが、8GBを超える大きなファイル(私のサイズ)を処理しなければならない状況に遭遇したらどうしますか? このような大きなファイルを扱う効率的​​なアプローチは何でしょうか?完全にメモリに収まらないファイルを解析する方法

私のフレームワーク:フレームワークに基づいて

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <time.h> 

int Parse(const char *filename, 
    const char *outputfile); 

int main(void) 
{ 
    clock_t t1 = clock(); 
    /* ............................................................................................................................. */ 
    Parse("file.txt", NULL); 
    /* ............................................................................................................................. */ 
    clock_t t2 = clock(); 
    fprintf(stderr, "time elapsed: %.4f\n", (double)(t2 - t1)/CLOCKS_PER_SEC); 
    fprintf(stderr, "Press any key to continue . . . "); 
    getchar(); 
    return 0; 
} 

long GetFileSize(FILE * fp) 
{ 
    long f_size; 
    fseek(fp, 0L, SEEK_END); 
    f_size = ftell(fp); 
    fseek(fp, 0L, SEEK_SET); 
    return f_size; 
} 

char *dump_file_to_array(FILE *fp, 
    size_t f_size) 
{ 
    char *buf = (char *)calloc(f_size + 1, 1); 
    if (buf) { 
     size_t n = 0; 
     while (fgets(buf + n, INT_MAX, fp)) { 
      n += strlen(buf + n); 
     } 
    } 
    return buf; 
} 

int Parse(const char *filename, 
    const char *outputfile) 
{ 
    /* open file for reading in text mode */ 
    FILE *fp = fopen(filename, "r"); 
    if (!fp) { 
     perror(filename); 
     return 1; 
    } 
    /* store file in dynamic memory and close file */ 
    size_t f_size = GetFileSize(fp); 
    char *buf = dump_file_to_array(fp, f_size); 
    fclose(fp); 
    if (!buf) { 
     fputs("error: memory allocation failed.\n", stderr); 
     return 2; 
    } 
    /* state machine variables */ 
    // ........ 

    /* array index variables */ 
    size_t x = 0; 
    size_t y = 0; 
    /* main loop */ 
    while (buf[x]) { 
     switch (buf[x]) { 
      /* ... */ 
     } 
     x++; 
    } 
    /* NUL-terminate array at y */ 
    buf[y] = '\0'; 
    /* write buffer to file and clean up */ 
    outputfile ? fp = fopen(outputfile, "w") : 
       fp = fopen(filename, "w"); 
    if (!fp) { 
     outputfile ? perror(outputfile) : 
        perror(filename); 
    } 
    else { 
     fputs(buf, fp); 
     fclose(fp); 
    } 
    free(buf); 
    return 0; 
} 

パターン削除機能は:あなたは、現在のデザインに固執する場合

int delete_pattern_in_file(const char *filename, 
    const char *pattern, const char *outputfile) 
{ 
    /* open file for reading in text mode */ 
    FILE *fp = fopen(filename, "r"); 
    if (!fp) { 
     perror(filename); 
     return 1; 
    } 
    /* copy file contents to buffer and close file */ 
    size_t f_size = GetFileSize(fp); 
    char *buf = dump_file_to_array(fp, f_size); 
    fclose(fp); 
    if (!buf) { 
     fputs("error - memory allocation failed", stderr); 
     return 2; 
    } 
    /* delete first match */ 
    size_t n = 0, pattern_len = strlen(pattern); 
    char *tmp, *ptr = strstr(buf, pattern); 
    if (!ptr) { 
     fputs("No match found.\n", stderr); 
     free(buf); 
     return -1; 
    } 
    else { 
     n = ptr - buf; 
     ptr += pattern_len; 
     tmp = ptr; 
    } 
    /* delete the rest */ 
    while (ptr = strstr(ptr, pattern)) { 
     while (tmp < ptr) { 
      buf[n++] = *tmp++; 
     } 
     ptr += pattern_len; 
     tmp = ptr; 
    } 
    /* copy the rest of the buffer */ 
    strcpy(buf + n, tmp); 
    /* open file for writing and print the processed buffer to it */ 
    outputfile ? fp = fopen(outputfile, "w") : 
       fp = fopen(filename, "w"); 
    if (!fp) { 
     outputfile ? perror(outputfile) : 
        perror(filename); 
    } 
    else { 
     fputs(buf, fp); 
     fclose(fp); 
    } 
    free(buf); 
    return 0; 
} 
+2

通常、flex/yaccを使用してイベントベースのパーサを作成します。これらはRAM(スタックなどのトークン)に必要な情報だけを保持します。正確には主に文法に依存します。 – Ctx

+0

オペレーティングシステム固有の可能性があります。 Linuxのいくつかの便利なsyscallsについて言及している[この回答](http://stackoverflow.com/a/41237690/841108)も参照してください。しかし、おそらく、ファイルを行単位で読むことができます。 [getline(3)](http://man7.org/linux/man-pages/man3/getline.3.html)を参照してください。 [その答え](http://stackoverflow.com/a/41208995/841108)の参考文献も見てください。 –

+1

そして、解析されたテキストファイルの構文と語彙を定義する必要があります。 –

答えて

11

、オプションはmmap()に代わりに読書のファイルであるかもしれませんそれをメモリバッファに入れる。

あなたはに機能dump_file_to_arrayを変更することができ、次の(Linux固有):

char *dump_file_to_array(FILE *fp, size_t f_size) { 
    buf = mmap(NULL, f_size, PROT_READ, MAP_SHARED, fileno(fp), 0); 
    if (buf == MAP_FAILED) 
     return NULL; 
    return buf; 
} 

は今、あなたは、ファイルを読み返すことができ、メモリマネージャは、ファイルの関連ポーションを保持し、自動的に取るだけに気になりますメモリ内にある。 Windowsの場合、同様のメカニズムが存在します。

+0

しかし、このバッファはヌルで終了するので、パーサーは、行または文字列ターミネータの存在に頼るのではなく、各バイトのファイルサイズとオフセットを比較する必要があります。 – chqrlie

+0

@chqrlie実際、すべてのケースでゼロで終了します。ファイルがページサイズの倍数でない場合しかし、そうであればそうではないかもしれません。 – Ctx

+0

@chux:ファイルサイズがページサイズの倍数でない場合は、mmapped領域の終わりを超えてバイトを読み取っても問題ないとは確信していません。ほとんどのシステムでは問題ありませんが、粒度の細かいシステムでセグメンテーション違反が発生する可能性があります。 – chqrlie

2

ファイルを1行ずつ解析している可能性があります。だから大きなブロック(4kまたは16k)で読み込み、その中のすべての行を解析します。小さな剰余を4kまたは16kバッファの先頭にコピーし、残りのバッファを読み込みます。すすぎ、繰り返します。

JSONまたはXMLの場合、複数のブロックまたは入力を受け入れることができるイベントベースのパーサーが必要です。

1

まずは、大きなファイルをRAMに保存するのではなく、ストリームを使用することをおすすめします。これは、通常、バッファリングはカーネルだけでなくライブラリによっても行われるためです。

ファイルに順番にアクセスしている場合は、現代のすべてのシステムで先読みアルゴリズムが実装されていることが分かります。そのため、事前にファイル全体を読み込むだけでほとんどの場合、 。

あなたはので、私は

std::ifstream 

のようなストリームを使ってその場で解析を行うことは、あなたのニーズに合うことを想定する必要がありますするつもりですカバーするために持っているユースケースを指定しませんでした。副次的なこととして、ファイルサイズが大きくなると予想される操作が別のスレッドで行われていることを確認してください。

+2

'std :: ifstream'はC++です、いいえ? –

+0

@ machine_1はい、それはC++です、fgetsは大丈夫ですので、RAMにすべてコピーする必要はありません。本当に必要な場合は、MAP_HUGETLBでmmapしようとするかもしれませんが、システムに十分なメモリがない場合にも失敗します。 –

2

あなたのアプローチには複数の問題があります。技術的に、あなたはRAMのサイズによって制限されていないが、メモリの量によって、ご使用の環境では、割り当てできるようになると、あなたのプログラムのために使用します。

最大利用可能メモリの概念はそれほど明らかではありません。これはさまざまな要因に依存します:

  • 32ビットコード用にコンパイルすると、プログラムにアクセスできる最大メモリサイズは4 GB未満に制限されます。それ。
  • プログラムが使用できるようにシステムが設定されているクォータ。これは利用可能なメモリより少なくなることがあります。
  • 物理メモリより多くのメモリが要求された場合にシステムが使用する戦略:現代のシステムでは、仮想メモリを使用し、プロセスやシステムタスク(ディスクキャッシュなど)間で物理メモリを共有します。数行。いくつかのシステムでは、マザーボードに物理的にインストールされているメモリよりも多くのメモリを割り当てて使用することができます。メモリにアクセスするたびにメモリページをディスクにスワップします。

    • longは、ファイルのサイズを保持するには小さすぎるかもしれませんタイプ:

    あなたのコード内でさらに問題があるWindowsシステム上に、longは32ビット64上にも 2GBを超えるチャンクでメモリを割り当てることができます。異なるAPIを使用して、システムからファイルサイズを要求する必要があります。

  • fgets()への一連の呼び出しでファイルを読んでいます。これは非効率的で、fread()への1回の呼び出しで十分です。さらに、ファイルにヌルバイト( '\ 0'文字)が埋め込まれていると、ファイルのチャンクがメモリに存在しなくなります。しかし、strstr()strcpy()などの文字列関数を使用して文字列削除タスクを処理すると、埋め込みヌルバイトを処理できませんでした。
  • while (ptr = strstr(ptr, pattern))の条件は割り当てになります。厳密には間違っているわけではありませんが、コードの読者を混乱させ、そのような割り当て条件がコーディングエラーであるコンパイラによる命取り警告を防ぐので、スタイルが貧弱です。あなたはそれが起こることは決してないと思うかもしれませんが、誰でもタイプミスを犯す可能性があり、テスト中に=が見つからないと悲惨な結果を招きます。
  • あなたif文の代わりに三項演算子の短手の使用があまりにも非常に紛らわしいです:outputfile ? fp = fopen(outputfile, "w") : fp = fopen(filename, "w");
  • は、代わりに、入力ファイルを書き換えるあまりにも危険です:何かがうまくいかない場合は、入力ファイルが失われます。

あなたは、バッファなし、非効率的とはいえ、その場でフィルタリングを実装できることに注意してください:

#include <stdio.h> 
#include <string.h> 

int main(int argc, char *argv[]) { 
    if (argc < 2) { 
     fprintf(stderr, "usage: delpat PATTERN <inputfile> outputfile\n"); 
     return 1; 
    } 
    unsigned char *pattern = (unsigned char*)argv[1]; 
    size_t i, j, n = strlen(argv[1]); 
    size_t skip[n + 1]; 
    int c; 

    skip[0] = 0; 
    for (i = j = 1; i < n; i++) { 
     while (memcmp(pattern, pattern + j, i - j)) { 
      j++; 
     } 
     skip[i] = j; 
    } 

    i = 0; 
    while ((c = getchar()) != EOF) { 
     for (;;) { 
      if (i < n && c == pattern[i]) { 
       if (++i == n) { 
        i = 0; /* match found, consumed */ 
       } 
       break; 
      } 
      if (i == 0) { 
       putchar(c); 
       break; 
      } 
      for (j = 0; j < skip[i]; j++) { 
       putchar(pattern[j]); 
      } 
      i -= skip[i]; 
     } 
    } 
    for (j = 0; j < i; j++) { 
     putchar(pattern[j]); 
    } 
    return 0; 
} 
0

代替ソリューション:あなたがLinuxシステムにしている、とあなたがのまともな量を持っている場合スワップスペースは、悪い少年全体を開くだけです。それはあなたのラムを消費し、またハードドライブのスペース(スワップ)を消費します。したがって、あなたは一度にすべてのものを開くことができます。ただそのすべてがラムにあるわけではありません。

賛否

  • 予期しないシャットダウンが発生した場合、スワップ領域のメモリが回復可能です。
  • RAMは、アプリケーションがあなたができるでしょう彼らは
  • を実行するためのRAMでの余地はないであろうため、ウイルスがコンピュータに損害を与えることができませんでした
  • あなたの高価な機器にあまり負担をかけることになりのでHDDは、安価であり、高価であり、スワップ領域を使用してLinuxオペレーティングシステムを最大限に活用します。通常、スワップスペースモジュールは使用されておらず、すべてが貴重なRAMを詰まらせます。
  • ラム全体を利用するために必要な付加的なエネルギーは、直接の領域を温めることができます。冬季に役立つ
  • "Complex and Special Memory Allocation Engineering"を履歴書に追加することができます。

短所

  • なしは
0

の外部アレイとしてファイルを処理することを検討してください。

コードでは、行インデックスの配列を使用できます。この索引配列は、大きなファイルのサイズの一部分でメモリに保持することができます。 行へのアクセスは、このルックアップを使用して、fsetpos()fread()/fgets()のシークですばやく実行されます。行が編集されると、新しい行は任意の順序で一時的なテキストファイルに保存されます。ファイルを保存すると、元のファイルと一時ファイルの両方が順番に読み込まれ、新しいファイルが作成され、書き込まれます。

typedef struct { 
    int attributes; // not_yet_read, line_offset/length_determined, 
        // line_changed/in_other_file, deleted, etc. 
    fpos_t line_offset; // use with fgetpos() fsetpos() 
    unsigned line_length; // optional field as code could re-compute as needed. 
} line_index; 

size_t line_count; 
// read some lines 
line_index *index = malloc(sizeof *index * line_count); 
// read more lines 
index = realloc(index, sizeof *index * line_count); 
// edit lines, save changes to appended temporary file. 
// ... 
// Save file -weave the contents of the source file and temp file to the new output file. 

また、巨大ファイルで、配列line_index[]自体があまりにディスクメモリで実現することができます。へのアクセスは簡単に計算されます。極端な意味では、ファイルの1 のみがいつでもメモリに保存されている必要があります。

0

あなたはstate-machineについて言及しました。すべての有限状態オートマトンは、先読みを最小限に抑えるように最適化することができます。

Lexでこれを行うことはできますか?コンパイルできる出力ファイルが生成されます。

あなたはレックスを使用したくない場合は、常に次のことが可能です。

  1. 読むのn文字を、nは、パターンの大きさである(リング?)バッファへ。マッチ後藤1
  2. プリントバッファ[0]、文字を読み取る場合
  3. が遅くなることはstrstr非常に長いパターンと縮退入力にもジャンプ2

、パターン

  • とバッファと一致するようにしてください。その場合は、より高度な刺しゅうのマッチングを調べることをお勧めします。

  • 0

    mmap()は、大きなサイズのファイルを処理するかなり良い方法です。 多くの柔軟性を提供しますが、ページサイズには注意が必要です。 Hereは、より詳細なことについて話す良い記事です。

    関連する問題