10

時期尚早と思われるメモリ不足の例外が原因で、さまざまな .NET コンストラクトのメモリ使用量を綿密に調査してきました。特に、ラージ オブジェクト ヒープを断片化する傾向がある大きなオブジェクトは、時期尚早のメモリ不足の例外を引き起こします。少し驚くべき領域の 1 つは、.NET の画像クラス (ビットマップとメタファイル) です。

私たちが学んだことは次のとおりですが、検証するための MS ドキュメントを見つけることができませんでした。

(1) 圧縮されたラスター ファイル (JPG、PNG、GIF など) から Bitmap オブジェクトを作成すると、そのファイルの最大解像度で、完全に圧縮されていないピクセル配列のメモリが消費されます。したがって、たとえば、9000x3000 ピクセルの 5MB JPG は 9000x3000x3 バイト (24 ビット カラー、アルファなしと仮定) に拡張されるか、81MB のメモリが消費されます。正しい?

(1a) 元の圧縮形式も保存されているという証拠 (以下の 2b を参照) があります。この場合、実際には 86MB です。しかし、それは不明です...誰か知っていますか?

(2) Metafile オブジェクトを作成し、ラスター ファイル (JPG、PNG、GIF など) をそこに描画すると、圧縮ファイルのメモリのみが消費されます。したがって、9000x3000 ピクセルの 5MB の JPG をメタファイルに描画すると、約 5MB のメモリしか消費しません。正しい?

(2a) ラスター ファイルをメタファイル オブジェクトに描画するには、ビットマップをファイルと共に読み込み、ビットマップをメタファイルに描画するしかないようです。その巨大なビットマップ データを一時的にロードする (および関連するメモリの断片化を引き起こす) ことを伴わない、より良い方法はありますか?

(2b) ビットマップをメタファイルに描画すると、元の圧縮ファイルと同様のサイズの圧縮形式が使用されます。元の圧縮ファイルをビットマップに保存することでそれを行いますか? それとも、元の圧縮設定を使用して展開されたビットマップを再圧縮することによってそれを行いますか?

(3) 当初、大きな (>85KB) イメージ オブジェクトはラージ オブジェクト ヒープに配置されると想定していました。実際、そうではないようです。むしろ、各ビットマップと各メタファイルは、実際のデータを含むネイティブ メモリのブロックを参照するスモール オブジェクト ヒープ内の 24 バイトのオブジェクトです。正しい?

(3a) このようなネイティブ メモリは、圧縮できないという点でラージ オブジェクト ヒープに似ていると想定しています...大きなオブジェクトがネイティブ メモリに配置されると、決して移動されないため、ネイティブ メモリの断片化は多くの問題を引き起こす可能性があります。ラージ オブジェクト ヒープの断片化。真実?または、基礎となるビットマップ/メタファイル データのより効率的な特別な処理はありますか?

(3b) したがって、別々に管理される 4 つの独立したメモリ ブロックが存在するようであり、それぞれが不足すると、同じメモリ不足例外が発生する可能性があります。オブジェクト ヒープ (GC によって収集されたが圧縮されていない 85KB を超えるマネージド オブジェクト)、ネイティブ メモリ (アンマネージド オブジェクト、おそらく圧縮されていない)、デスクトップ ヒープ (ウィンドウ ハンドルなどの限られたリソースが管理される場所)。これらの 4 つを適切に文書化しましたか? 他に知っておくべきことはありますか?

上記について誰でも提供できる明確さは大歓迎です。以上のことを詳しく説明している本や記事があれば教えてください。(私は必要な読書を喜んで行いますが、大部分の本はそれほど深く理解していないため、私がまだ知らないことは何も教えてくれません。)

ありがとう!

4

2 に答える 2

4

画像データを保存するには、ピクセルとして保存する方法とベクトルとして保存する方法の 2 つがあります。Bitmapはピクセルに関するものでMetafile、ピクセルベクトルに関するものです。ベクターデータは、格納するのにはるかに効率的です。

ビットマップを操作できるようにするには、データを圧縮せずにメモリに格納する必要があります。それ以外GetPixelの場合SetPixelは、変更ごとにビットマップを解凍、変更、再圧縮する必要があります (最初から可能であれば)。

メタファイルは Microsoft によって作成され、GDI で動作することを目的としているため、グラフィックス カードで直接動作するメモリ効率の高い圧縮アルゴリズムが組み込まれている可能性があります。また、GetPixel SetPixelメタファイルにはメソッドがないため、操作を可能にするためにメモリ内で圧縮解除する必要はありません。


ランタイムが使用するメモリ プールを気にする必要はありません。さらに多くの方法があり、ランタイムはオブジェクトを配置する場所を決定します。また、 (大きな) オブジェクトを使用することで発生する可能性のあるメモリ不足の例外についても気にする必要はありません。ランタイムは、メモリ不足の例外が発生しないようにするために、できる限りのことを行います (オブジェクトを他のオブジェクト間のギャップに配置する、ヒープを圧縮する、利用可能な仮想メモリを拡張する)。なんらかの理由でこのような例外が発生した場合は、コードに修正が必要な別の問題 (メモリ リークなど) が存在する可能性があります。

メモリ ヒープ、マップ、テーブルの概要: ( source )

.NET で使用されるヒープ、マップ、およびテーブル


また、85 KiB を超えるオブジェクトがラージ オブジェクト ヒープに配置されるという仮定は、完全には正しくありません。現在のバージョンの CLR のほとんどのオブジェクトではこれが正しいですが、たとえば 8 KiB の double の配列 (1000 double) もラージ オブジェクト ヒープに割り当てられます。ランタイムがこれに関与するようにしてください。

于 2013-03-20T17:17:36.087 に答える
2

私はこれらのいくつかに対する答えを知っています:

(1) はい、それがビットマップ イメージの定義です。

(3) はい、それが Bitmap が IDisposable インターフェイスを実装する理由です。

(3a) それは驚くべきことです。Bitmap オブジェクトを使い終わったら、そのオブジェクトに対して Dispose() メソッドを実行していますか?

(3b) 少なくともその 4 つは、はい。

于 2013-03-20T17:10:52.117 に答える