6

解凍するためにftpからダウンロードした大きなgzipファイル(約10MB〜200MB)がたくさんあります。

そこで、グーグルでgzip解凍の解決策を見つけようとしました。

    static byte[] Decompress(byte[] gzip)
    {
        using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
        {
            const int size = 4096;
            byte[] buffer = new byte[size];
            using (MemoryStream memory = new MemoryStream())
            {
                int count = 0;
                do
                {
                    count = stream.Read(buffer, 0, size);
                    if (count > 0)
                    {
                        memory.Write(buffer, 0, count);
                    }
                }
                while (count > 0);
                return memory.ToArray();
            }
        }
    }

50mb未満のファイルではうまく機能しますが、50mbを超える入力を行うと、システムのメモリ不足の例外が発生します。最後の位置と例外前のメモリの長さは134217728です。物理メモリとは関係がないと思います。32ビットを使用しているため、2GBを超えるオブジェクトを使用できないことを理解しています。

また、ファイルを解凍した後にデータを処理する必要があります。ここでメモリストリームが最善のアプローチであるかどうかはわかりませんが、ファイルに書き込んでからファイルを再度読み取るのはあまり好きではありません。

私の質問

  • System.OutMemoryExceptionが発生したのはなぜですか?
  • gzipファイルを解凍し、後でテキスト処理を行うための最善の解決策は何ですか?
4

4 に答える 4

4

MemoryStreamのメモリ割り当て戦略は、大量のデータには適していません。

MemoryStreamのコントラクトは、基になるストレージとして連続した配列を持つことであるため、大規模なストリーム(多くの場合log2(size_of_stream))に対して十分な頻度で配列を再割り当てする必要があります。このような再割り当ての副作用は次のとおりです。

  • 再割り当てでの長いコピー遅延
  • 新しいアレイは、以前の割り当てによってすでに大幅に断片化されている空きアドレス空間に収まる必要があります
  • 新しいアレイは、癖のあるLOHヒープ上にあります(圧縮なし、GC2での収集)。

その結果、MemoryStreamを介して大きな(100Mb +)ストリームを処理すると、x86システムでメモリ不足の例外が発生する可能性があります。さらに、データを返す最も一般的なパターンは、GetArrayを呼び出す方法です。これには、MemoryStreamに使用される最後の配列バッファーとほぼ同じ量のスペースが追加で必要になります。

解決するためのアプローチ:

  • 最も安価な方法は、MemoryStreamを必要な概算サイズ(できれば少し大きい)に事前成長させることです。何も保存していない偽のストリームを読み取ることで必要なサイズを事前に計算できます(CPUリソースの浪費ですが、読み取ることはできます)。バイト配列の代わりにストリームを返すことも検討してください(または、MemoryStreamバッファーのバイト配列を長さとともに返します)。
  • ストリーム全体またはバイト配列が必要な場合に処理する別のオプションは、MemoryStreamの代わりに一時ファイルストリームを使用して大量のデータを格納することです。
  • より複雑なアプローチは、基になるデータをより小さな(つまり、64K)ブロックにチャンク化するストリームを実装して、ストリームの拡張が必要な​​ときにLOHへの割り当てとデータのコピーを回避することです。
于 2012-05-03T02:03:52.690 に答える
1

次のようなテストを試して、OutOfMemoryExceptionを取得する前にMemoryStreamに書き込むことができる量を確認できます。

        const int bufferSize = 4096;
        byte[] buffer = new byte[bufferSize];

        int fileSize = 1000 * 1024 * 1024;

        int total = 0;

        try
        {
            using (MemoryStream memory = new MemoryStream())
            {
                while (total < fileSize)
                {
                    memory.Write(buffer, 0, bufferSize);
                    total += bufferSize;
                }

            }

            MessageBox.Show("No errors"); 

        }
        catch (OutOfMemoryException)
        {
            MessageBox.Show("OutOfMemory around size : " + (total / (1024m * 1024.0m)) + "MB" ); 
        }

最初に一時的な物理ファイルに解凍して、小さなチャンクで再読み取りし、処理を進めていく必要がある場合があります。

サイドポイント:興味深いことに、Windows XP PCでは、上記のコードは、コードが.net 2.0をターゲットにしている場合は「OutOfMemoryサイズ256MB前後」、.net4の場合は「OutOfMemoryサイズ512MB前後」になります。

于 2012-05-03T02:03:30.580 に答える
1

たまたま複数のスレッドでファイルを処理していますか?それはあなたのアドレス空間の大量を消費するでしょう。OutOfMemoryエラーは通常、物理メモリとは関係がないため、MemoryStreamは予想よりもはるかに早くなくなる可能性があります。このディスカッションを確認してくださいhttp://social.msdn.microsoft.com/Forums/en-AU/csharpgeneral/thread/1af59645-cdef-46a9-9eb1-616661babf90。64ビットプロセスに切り替えた場合は、処理しているファイルサイズに問題がない可能性があります。

ただし、現在の状況では、メモリマップトファイルを使用して、アドレスサイズの制限を回避できます。.NET 4.0を使用している場合は、Windows関数http://msdn.microsoft.com/en-us/library/dd267535.aspxのネイティブラッパーが提供されます。

于 2012-05-03T02:06:41.587 に答える
-1

32ビットを使用しているため、2GBを超えるオブジェクトを使用できないことを理解しています

それは正しくありません。必要なだけのメモリを持つことができます。32ビットの制限は、4GB(OSはその半分を占める)の仮想アドレス空間しか持てないことを意味します。仮想アドレス空間はメモリではありません。ここに良い読み物があります。

System.OutMemoryExceptionが発生したのはなぜですか?

アロケータがオブジェクトの連続したアドレス空間を見つけることができなかったか、発生が速すぎて詰まるためです。(おそらく最初のもの)

gzipファイルを解凍し、後でテキスト処理を行うための最善の解決策は何ですか?

ファイルをダウンロードするスクリプトを作成し、gzipや7zipなどのツールを使用して解凍してから処理します。処理の種類、ファイルの数、および合計サイズに応じて、この種類のメモリの問題を回避するために、ある時点でそれらを保存する必要があります。解凍後に保存し、一度に1MBを処理します。

于 2012-05-03T01:16:49.290 に答える