3

レコードを行ごとに解析し、検証して別のファイルに書き込む巨大な関数があるコードに状況があります。

ファイルにエラーがある場合、レコードを拒否する別の関数を呼び出し、拒否理由を書き込みます。

プログラムのメモリ リークにより、SIGSEGV でクラッシュします。クラッシュした場所からファイルを「再起動」するための 1 つの解決策は、最後に処理されたレコードを単純なファイルに書き込むことでした。

これを実現するには、処理ループ内の現在のレコード番号をファイルに書き込む必要があります。ループ内のファイルでデータが上書きされるようにするにはどうすればよいですか?

ループ内で最初の位置/巻き戻しに fseek を使用すると、パフォーマンスが低下しますか?

レコードの数は、場合によってはロットにすることができます (最大 500K)。

ありがとう。

編集: メモリ リークは既に修正されています。再起動ソリューションは、追加の安全対策および SKIP n レコード ソリューションと共に再起動メカニズムを提供する手段として提案されました。先に言及せずに申し訳ありません。

4

5 に答える 5

6

この種の問題に直面した場合、次の 2 つの方法のいずれかを採用できます。

  1. あなたが提案した方法:読み取ったレコードごとに、レコード番号(または入力ファイルで返された位置)を別のブックマークファイルに書き出します。中断したところから正確に再開するには、重複したレコードを導入しないようにするため、書き込みのたびに (および出力/拒否ファイルの両方に) 書き込みを行う必要があります。これと、一般的にバッファリングされていない書き込み操作は、典型的な (失敗のない) 速度を低下させます。 ) シナリオが大幅に。完全を期すために、ブックマーク ファイルに書き込むには 3 つの方法があることに注意してください。 ftellfflushbookmark
    • fopen(..., 'w') / fwrite / fclose- 非常に遅い
    • rewind / truncate / fwrite / fflush- わずかに高速
    • rewind / fwrite / fflush-やや速い; truncateレコード番号 (またはftell位置) は常に前のレコード番号 (または位置) と同じかそれ以上の長さになるため、スキップできftellます。起動時にファイルを切り捨てれば、完全に上書きされます(これは元の質問に答えます)。
  2. ほとんどの場合、すべてがうまくいくと仮定します。失敗後に再開するときは、すでに出力されているレコードの数(通常の出力とリジェクトを加えたもの) を単純にカウントし、入力ファイルから同等の数のレコードをスキップします。
    • これにより、障害後の再開シナリオの場合にパフォーマンスを大幅に損なうことなく、典型的な (障害のない) シナリオを非常に高速に保つことができます。
    • ファイルを作成する必要はありませんfflush。少なくともそれほど頻繁ではありません。fflushリジェクト ファイルへの書き込みに切り替える前にメインの出力ファイルが必要であり、メインの出力ファイルへの書き込みfflushに戻る前にリジェクト ファイルが必要です (おそらく、500k レコードの入力の場合、数百回または数千回)。出力/拒否ファイルからの最後の未終了行、その行までのすべてが一貫しています。

方法 #2 を強くお勧めします。方法 1 に伴う書き込み (3 つの可能性のいずれか) は、方法 2 で必要な追加の (バッファリングされた) 読み取りに比べて非常にコストfflushがかかります (数ミリ秒かかる場合があります。これに 500k を掛けると数分になります。 500k レコードのファイル内の行はほんの数秒しかかからず、さらに、ファイルシステムのキャッシュはあなたに反対するのではなく、一緒に働いています。)


編集 方法2を実装するために必要な正確な手順を明確にしたかっただけです:

  • 出力ファイルと拒否ファイルにそれぞれ書き込むときは、あるファイルへの書き込みから別のファイルへの書き込みに切り替えるときにのみフラッシュする必要があります。これらのフラッシュ オン ファイル スイッチを実行する必要性を示す例として、次のシナリオを検討してください。

    • メイン出力ファイルに 1000 レコードを書き込むとします。
    • 最初にメイン出力ファイルを手動でフラッシュせずに、rejects ファイルに 1 行書き込む必要があります。
    • 最初に拒否ファイルを手動でフラッシュせずに、メイン出力ファイルにさらに 200 行を書き込みます。
    • メイン出力ファイルのバッファに大量のデータ (つまり 1200 レコード) が蓄積されているため 、ランタイムは自動的にメイン出力ファイルをフラッシュします。
      • ただし、ランタイムは拒否ファイルをディスクに自動的にフラッシュしていません。これは、ファイル バッファーに含まれるレコードが 1 つだけであり、自動的にフラッシュするのに十分なボリュームではないためです。
    • この時点でプログラムがクラッシュします
    • 再開して、メイン出力ファイルで 1200 レコードをカウントします (ランタイムがそれらをフラッシュします) が、リジェクト ファイルでは 0 (!) レコード (フラッシュされません)。
    • メイン出力ファイルに正常に処理されたレコードが 1200 しかないと仮定して、レコード #1201 で入力ファイルの処理を再開します。拒否されたレコードは失われ、1200 番目の有効なレコードが繰り返されます。
    • あなたはこれを望んでいません!
  • 出力/拒否ファイルを切り替えた後、手動でフラッシュすることを検討してください:
    • メイン出力ファイルに 1000 レコードを書き込むとします。
    • rejects ファイルに属する無効なレコードが 1 つ見つかりました。最後のレコードは有効でした。これは、rejects ファイルへの書き込みに切り替えていることを意味します。rejects ファイルに書き込む前にメイン出力ファイルをフラッシュします。
    • 次に、rejects ファイルに 1 行書き込みます。
    • メイン出力ファイルに属する有効なレコードが 1 つ見つかります。最後のレコードが無効です。これは、メイン出力ファイルへの書き込みに切り替えていることを意味します: メイン出力ファイルに書き込む前に拒否ファイルをフラッシュします
    • 最初に拒否ファイルを手動でフラッシュせずに、メイン出力ファイルにさらに 200 行を書き込みます。
    • メイン出力ファイルでの最後の手動フラッシュ以降にバッファリングされた 200 レコードでは、自動フラッシュをトリガーするのに十分ではないため、ランタイムが自動的に何もフラッシュしなかったと仮定します。
    • この時点でプログラムがクラッシュします
    • メイン出力ファイルで 1000 の有効なレコードを再開してカウントし (rejects ファイルに切り替える前に手動でフラッシュした)、rejects ファイルで 1 つのレコードをカウントします (メイン出力ファイルに戻す前に手動でフラッシュしました)。
    • 無効なレコードの直後の最初の有効なレコードであるレコード #1001 で、入力ファイルの処理を正しく再開します。
    • 次の 200 の有効なレコードはフラッシュされなかったため再処理しますが、欠落したレコードも重複も取得しません。
  • ランタイムの自動フラッシュの間隔に満足できない場合は、100 または 1000 レコードごとに手動でフラッシュすることもできます。これは、レコードの処理がフラッシュよりもコストがかかるかどうかによって異なります (処理がよりコストがかかる場合は、レコードごとに頻繁にフラッシュし、そうでない場合は、出力/拒否を切り替えるときにのみフラッシュします)。

  • 失敗からの再開

    • 読み取りと書き込みの両方で出力ファイルと rejects ファイルを開き、ファイルrecords_resume_counterの終わりに到達するまで各レコードの読み取りとカウントを開始します (たとえば)。
    • 出力している各レコードの後に​​フラッシュしていない限り、出力ファイルと拒否ファイルの両方の最後のレコードに対して少し特別な処理を実行する必要があります。
      • 中断された出力/拒否ファイルからレコードを読み取る前に、その出力/拒否ファイル内の現在の位置を覚えておいてください (使用ftell)、それを呼び出しましょうlast_valid_record_ends_here
      • 記録を読む。レコードが部分的なレコードではない (つまり、ランタイムがファイルをレコードの途中までフラッシュしていない) ことを検証します。
      • \n1 行に 1 つのレコードがある場合、これは、レコードの最後の文字がキャリッジ リターンまたはライン フィード (または「r」)であることを確認することで簡単に確認できます。
        • レコードが完了している場合は、レコード カウンターをインクリメントし、次のレコード (またはファイルの終わりのいずれか早い方) に進みます。
        • レコードが部分的である場合は、にfseek戻りlast_valid_record_ends_here、この出力/拒否ファイルからの読み取りを停止します。カウンターをインクリメントしません。次の出力に進むか、ファイルをすべて拒否しない限り、ファイルを拒否します
    • 読み取り用に入力ファイルを開き、records_resume_counterそこからレコード をスキップします
      • 処理を続行し、output/rejects ファイルに出力します。これは、すでに処理されたレコードの読み取り/カウントを中断した出力/拒否ファイルに自動的に追加されます
      • 部分的なレコードのフラッシュに対して特別な処理を実行する必要がある場合、出力する次のレコードは、前回の実行 (でlast_valid_record_ends_here) からの部分的な情報を上書きします。重複したレコード、不要なレコード、欠落したレコードはありません。
于 2009-03-03T06:03:25.550 に答える
2

レコード全体を書き出すよりも、それぞれの先頭で ftell() を呼び出して、ファイル ポインタの位置を書き出す方がおそらく簡単でしょう。プログラムを再起動する必要がある場合は、 fseek() をファイル内の最後に書き込まれた位置に移動して続行します。

もちろん、メモリリークを修正するのが最善です;)

于 2009-03-03T05:53:15.560 に答える
2

最後に処理されたレコードをファイルに書き込むようにコードを変更できる場合、メモリ リークを修正するようにコードを変更できないのはなぜでしょうか?

症状を治療するよりも、問題の根本原因を修正する方が良い解決策のように思えます。

fseek()パフォーマンスは低下しfwrite()ますが、オープン/書き込み/クローズタイプの操作ほどではありません。

値を 2 番目のファイルに保存することを想定していftell()ます (中断したところから再開できるようにするため)。fflush()データが C ランタイム ライブラリから OS バッファに確実に書き込まれるように、ファイルも常に保持する必要があります。そうしないと、SEGV によって値が最新でないことが保証されます。

于 2009-03-03T05:52:38.527 に答える
0

すべてのレコードの最後に処理された位置を書き込むと、(通常はファイルを閉じることによって) 書き込みをコミットしてからファイルを再度開く必要があるため、パフォーマンスに顕著な影響があります。他の作品では、fseekはあなたの心配の最も少ないものです。

于 2009-03-03T05:56:14.307 に答える
0

より深い穴を掘るのをやめて、Valgrindを介してプログラムを実行するだけです。そうすることで、リークやその他の問題を回避できます。

于 2009-03-03T06:03:56.457 に答える