2

SharpDX を使用してタイル張りの 2D 画像をレンダリングする .NET 3.5 アプリケーションに取り組んでいます。

テクスチャ (Texture2D) はオンデマンドでキャッシュに読み込まれ、マネージド プールに作成されます。

テクスチャは不要になると破棄され、Dispose() が正しく呼び出されることを確認しました。SharpDX オブジェクト トラッキングは、ファイナライズ中のテクスチャがないことを示します。

問題は、テクスチャによって使用される大量のアンマネージ ヒープ メモリが、破棄後も引き続き予約されることです。このメモリは新しいテクスチャをロードするときに再利用されるため、メモリ リークは発生しません。

ただし、アプリケーションの別の部分でも、新しい画像を処理するためにかなりの量のメモリが必要です。これらのヒープはまだ存在しているため、テクスチャが破棄されても、別のイメージをロードするための十分な連続メモリがありません (数百 MB になる可能性があります)。

を使用してアンマネージ メモリを割り当てるAllocHGlobalと、結果のヒープ メモリは を呼び出した後に完全に消失しますFreeHGlobal

断片化

VMMap は、アプリケーションを頻繁に使用した後、管理されていないヒープ (赤) を示します。

アンマネージド ヒープ

この時点で実際にコミットされているのは約 20MB だけですが、管理されていないヒープが約 380MB を占めていることがわかります。

長期的には、アプリケーションは 64 ビットに移植されています。ただし、管理されていない依存関係があるため、これは簡単なことではありません。また、すべてのユーザーが 64 ビット マシンを使用しているわけではありません。

編集:この問題のデモをまとめました - WinForms アプリケーションを作成し、Nuget 経由で SharpDX 2.6.3 をインストールします。

Form1.cs:

using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;
using SharpDX.Direct3D9;

namespace SharpDXRepro {
    public partial class Form1 : Form {
        private readonly SharpDXRenderer renderer;
        private readonly List<Texture> textures = new List<Texture>();

        public Form1() {
            InitializeComponent();

            renderer = new SharpDXRenderer(this);

            Debugger.Break(); // Check VMMap here

            LoadTextures();

            Debugger.Break(); // Check VMMap here

            DisposeAllTextures();

            Debugger.Break(); // Check VMMap here

            renderer.Dispose();

            Debugger.Break(); // Check VMMap here
        }

        private void LoadTextures() {
            for (int i = 0; i < 1000; i++) {
                textures.Add(renderer.LoadTextureFromFile(@"D:\Image256x256.jpg"));
            }
        }

        private void DisposeAllTextures() {
            foreach (var texture in textures.ToArray()) {
                texture.Dispose();
                textures.Remove(texture);
            }
        }
    }
}

SharpDXRenderer.cs:

using System;
using System.Linq;
using System.Windows.Forms;
using SharpDX.Direct3D9;

namespace SharpDXRepro {
    public class SharpDXRenderer : IDisposable {
        private readonly Control parentControl;

        private Direct3D direct3d;
        private Device device;
        private DeviceType deviceType = DeviceType.Hardware;
        private PresentParameters presentParameters;
        private CreateFlags createFlags = CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded;

        public SharpDXRenderer(Control parentControl) {
            this.parentControl = parentControl;

            InitialiseDevice();
        }

        public void InitialiseDevice() {
            direct3d = new Direct3D();
            AdapterInformation defaultAdapter = direct3d.Adapters.First();

            presentParameters = new PresentParameters {
                Windowed = true,
                EnableAutoDepthStencil = true,
                AutoDepthStencilFormat = Format.D16,
                SwapEffect = SwapEffect.Discard,
                PresentationInterval = PresentInterval.One,
                BackBufferWidth = parentControl.ClientSize.Width,
                BackBufferHeight = parentControl.ClientSize.Height,
                BackBufferCount = 1,
                BackBufferFormat = defaultAdapter.CurrentDisplayMode.Format,
            };

            device = new Device(direct3d, direct3d.Adapters[0].Adapter, deviceType,
                parentControl.Handle, createFlags, presentParameters);
        }

        public Texture LoadTextureFromFile(string filename) {
            using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read)) {
                return Texture.FromStream(device, stream, 0, 0, 1, Usage.None, Format.Unknown, Pool.Managed, Filter.Point, Filter.None, 0);
            }
        }

        public void Dispose() {
            if (device != null) {
                device.Dispose();
                device = null;
            }

            if (direct3d != null) {
                direct3d.Dispose();
                direct3d = null;
            }
        }
    }
}

したがって、私の質問は - (どのように) テクスチャが破棄された後、これらのアンマネージ ヒープによって消費されたメモリを再利用できますか?

4

1 に答える 1

1

問題を引き起こしているメモリは、nVidia ドライバーによって割り当てられているようです。私が知る限り、すべての割り当て解除メソッドが適切に呼び出されるため、これはドライバーのバグである可能性があります。インターネットを見回すと、これに関連しているように見える問題がいくつか示されていますが、参照する価値があるほど深刻なものではありません. ATi カードでこれをテストすることはできません (10 年ほど前に見たことがありません :D)。

したがって、オプションは次のようになります。

  • テクスチャが「共有」ヒープに割り当てられないように十分な大きさであることを確認してください。これにより、メモリ リークの進行が大幅に遅くなります。まだ解放されていないメモリですが、経験しているほど深刻なメモリの断片化は発生しません。あなたはタイルの描画について話している - これは歴史的にタイルセットで行われており、より優れた処理を提供します (ただし、欠点もあります)。私のテストでは、単に小さなテクスチャを回避するだけで、問題はほぼ解消されました。それが単に隠されているのか、それとも完全になくなっているのかを判断するのは困難です (両方ともかなり可能性があります)。
  • 別のプロセスで処理を処理します。メイン アプリケーションは必要に応じて他のプロセスを起動し、ヘルパー プロセスが終了するとメモリは適切に再利用されます。もちろん、これは何らかの処理アプリケーションを作成している場合にのみ意味があります。実際にテクスチャを表示するものを作成している場合、これは役に立ちません (または、少なくともセットアップが非常に難しくなります)。
  • テクスチャを破棄しないでください。Managedテクスチャ プールは、デバイスとの間でテクスチャのページングを処理し、優先順位などを使用したり、デバイス上の (管理された) メモリ全体をフラッシュしたりすることもできます。これは、テクスチャがプロセスメモリに残ることを意味しますが、現在のアプローチよりもメモリ使用量が改善されるようです:)
  • おそらく、問題は、たとえば DirectX 9 コンテキストのみに関連している可能性があります。DX10 や DXGI などの新しいインターフェイスのいずれかでテストすることをお勧めします。これは、必ずしも DX10+ GPU に限定されるわけではありませんが、Windows XP のサポートは失われます (いずれにせよ、もはやサポートされていません)。
于 2015-07-03T14:37:38.627 に答える