web-dev-qa-db-ja.com

C#で大きなテキストファイルを効率的に書き込む方法

Google Product Feed のテキストファイルを生成するメソッドをC#で作成しています。フィードには30,000件以上のレコードが含まれ、現在のテキストファイルの重量は約7Mbです。

現在使用しているコードは次のとおりです(簡潔にするためにいくつかの行を削除しています)。

public static void GenerateTextFile(string filePath) {

  var sb = new StringBuilder(1000);
  sb.Append("availability").Append("\t");
  sb.Append("condition").Append("\t");
  sb.Append("description").Append("\t");
  // repetitive code hidden for brevity ...
  sb.Append(Environment.NewLine);

  var items = inventoryRepo.GetItemsForSale();

  foreach (var p in items) {
    sb.Append("in stock").Append("\t");
    sb.Append("used").Append("\t");
    sb.Append(p.Description).Append("\t");
    // repetitive code hidden for brevity ...
    sb.AppendLine();
  }

  using (StreamWriter outfile = new StreamWriter(filePath)) {
      result.Append("Writing text file to disk.").AppendLine();
      outfile.Write(sb.ToString());
  }
}

StringBuilderが仕事に適したツールかどうか疑問に思っています。代わりにTextWriterを使用すると、パフォーマンスが向上しますか?

IOパフォーマンスについてはあまり知りませんので、ヘルプや一般的な改善点はありがたいです。ありがとうございます。

35
jessegavin

ファイルI/O操作は、一般に最新のオペレーティングシステムで最適化されています。メモリ内のファイルの文字列全体をアセンブルしようとするべきではありません...それを少しずつ書き出すだけです。 FileStreamは、バッファリングおよびその他のパフォーマンスの考慮事項を処理します。

次のように移動することにより、この変更を簡単に行うことができます。

_using (StreamWriter outfile = new StreamWriter(filePath)) {
_

関数の先頭に移動し、代わりにファイルに直接書き込むStringBuilderを取り除きます。

メモリ内に大きな文字列を構築することを避けるべきいくつかの理由があります:

  1. StringBuilderは書き込み時に容量を増やす必要があるため、実際にパフォーマンスが低下する可能性があります。その結果、メモリの再割り当てとコピーが発生します。
  2. 物理的に割り当てられるよりも多くのメモリが必要になる場合があります。その結果、仮想メモリ(スワップファイル)が使用されることになり、RAMよりもかなり遅くなります。
  3. 本当に大きなファイル(> 2Gb)の場合、アドレス空間が不足し(32ビットプラットフォーム)、完了できません。
  4. StringBuilderの内容をファイルに書き込むには、ToString()を使用する必要があります。これは、両方のコピーが一定期間メモリ内に存在する必要があるため、プロセスのメモリ消費を効果的に2倍にします。この操作は、アドレス空間が十分に断片化されており、単一の連続したメモリブロックを割り当てることができない場合にも失敗する場合があります。
68
LBushkin

usingステートメントを移動して、コード全体を包含し、ファイルに直接書き込みます。すべてを最初にメモリに保持しても意味がありません。

26
Jon Skeet

すべてをStringBuilderにキャッシュするのではなく、StreamWriter.Writeを使用して一度に1つの文字列を書き込みます。

11
Alex Humphrey