SharpDX内存碎片

时间:2015-07-02 13:33:57

标签: c# memory directx-9 sharpdx memory-fragmentation

我正在开发一个使用SharpDX渲染平铺2D图像的.NET 3.5应用程序。

纹理(Texture2D)按需加载到缓存中,并在托管池中创建。

纹理在不再需要时被丢弃,我已经验证正确调用了Dispose()。 SharpDX对象跟踪表明没有最终确定纹理。

问题是纹理使用的大量非托管堆内存在处理后会继续保留。加载新纹理时会重复使用此内存,因此内存不会泄漏。

但是,应用程序的另一部分还需要大量内存来处理新映像。因为这些堆仍然存在,即使已经处理了纹理,也没有足够的连续内存来加载另一个图像(可能是几百MB)。

如果我使用AllocHGlobal分配非托管的meory,则生成 调用FreeHGlobal后,堆内存完全消失。

Fragmentation

VMMap在大量使用应用程序后显示非托管堆(红色)。

Unmanaged Heap

我们可以看到这里非托管堆约占380MB,即使此时实际上只提交了~20MB。

长期来看,应用程序正在移植到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;
            }
        }
    }
}

因此我的问题是 - (如何)在纹理被处理后我可以回收这些非托管堆消耗的内存?

1 个答案:

答案 0 :(得分:1)

似乎提供问题的内存是由nVidia驱动程序分配的。据我所知,所有释放方法都被正确调用,因此这可能是驱动程序中的错误。环顾互联网显示了一些与此相关的问题,尽管它没有什么严重的值得参考。我无法在ATi卡上测试这个(我在十年内没见过它:D)。

所以看起来你的选择是:

  • 确保您的纹理足够大,永远不会分配到&#34;共享&#34;堆。这使得内存泄漏的进行速度要慢得多 - 虽然它仍未释放内存,但它不会导致内存碎片的严重程度与您所经历的一样严重。您正在谈论绘制瓷砖 - 这在历史上已经完成了瓷砖设置,这可以让您更好地处理(尽管它们也有缺点)。在我的测试中,简单地避免微小的纹理,但却消除了这个问题 - 很难说它是隐藏还是完全消失(两者都很可能)。
  • 在单独的过程中处理您的处理。您的主应用程序将在需要时启动其他进程,并在助手进程退出时正确回收内存。当然,这只有在您编写某些处理应用程序时才有意义 - 如果您正在制作实际显示纹理的内容,那么这不会有帮助(或者至少是它的帮助)设置真的很棘手)。
  • 不要丢弃纹理。 Managed纹理池为您处理与设备之间的纹理分页,它甚至允许您使用优先级等,以及刷新整个设备(托管)内存。这意味着纹理将保留在您的进程内存中,但似乎您仍然可以获得比使用当前方法更好的内存使用情况:)
  • 可能问题可能与例如仅限DirectX 9上下文。您可能希望使用其中一个较新的接口进行测试,例如DX10或DXGI。这并不一定限制你使用DX10 + GPU - 但你将失去对Windows XP的支持(不管怎样都不支持)。