简体   繁体   English

SharpDX 内存碎片

[英]SharpDX memory fragmentation

I am working on a .NET 3.5 application which uses SharpDX to render tiled 2D images.我正在开发一个 .NET 3.5 应用程序,它使用 SharpDX 来呈现平铺的 2D 图像。

Textures (Texture2D) are loaded into a cache on-demand, and are created in the managed pool.纹理 (Texture2D) 按需加载到缓存中,并在托管池中创建。

Textures are disposed of when no longer required, and I have verified that Dispose() is called correctly.当不再需要时,纹理会被处理掉,并且我已经验证 Dispose() 被正确调用。 SharpDX object tracking indicates that there are no textures being finalized. SharpDX 对象跟踪表明没有纹理正在最终确定。

The issue is that large amounts of unmanaged heap memory used by the textures continues to be reserved after disposal.问题是纹理使用的大量非托管堆内存在处理后继续保留。 This memory is reused when loading a new texture, so memory is not being leaked.加载新纹理时会重用此内存,因此不会泄漏内存。

However, another part of the application also requires significant chunks of memory to process new images.但是,应用程序的另一部分也需要大量内存来处理新图像。 Because these heaps are still present, even though the textures have been disposed, there is not enough contiguous memory to load another image (can be hundreds of MB).因为这些堆仍然存在,即使纹理已被处理,也没有足够的连续内存来加载另一个图像(可能是数百 MB)。

If I allocate unmanaged meory using AllocHGlobal , the resulting heap memory completely vanishes again after calling FreeHGlobal .如果我使用AllocHGlobal分配非托管内存,则在调用FreeHGlobal后,生成的堆内存将再次完全消失。

碎片化

VMMap shows the unmanaged heap (red) after heavy usage of the application. VMMap 在大量使用应用程序后显示非托管堆(红色)。

非托管堆

We can see here that unmanaged heap accounts for ~380MB, even though only ~20MB is actually committed at this point.我们可以在这里看到,非托管堆占了大约 380MB,尽管此时实际上只有大约 20MB 被提交。

Long term, the application is being ported to 64-bit.从长远来看,该应用程序正在移植到 64 位。 However, this is not trivial due to unmanaged dependencies.但是,由于非托管依赖关系,这并非微不足道。 Also, not all users are on 64-bit machines.此外,并非所有用户都使用 64 位计算机。

EDIT: I've put together a demonstration of the issue - create a WinForms application and install SharpDX 2.6.3 via Nuget.编辑:我已经把这个问题的演示放在一起 - 创建一个 WinForms 应用程序并通过 Nuget 安装 SharpDX 2.6.3。

Form1.cs: 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: 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;
            }
        }
    }
}

My question therefore is - (how) can I reclaim the memory consumed by these unmanaged heaps after the textures have been disposed?因此,我的问题是 - (如何)在处理纹理后回收这些非托管堆消耗的内存?

It seems the memory that's giving the issues is allocated by the nVidia driver.似乎出现问题的内存是由 nVidia 驱动程序分配的。 As far as I can tell, all the deallocation methods are properly called, so this might be a bug in the drivers.据我所知,所有释放方法都被正确调用,所以这可能是驱动程序中的一个错误。 Looking around the internet shows some issues that seem related to this, though it's nothing serious enough to be worth referencing.环顾互联网,发现了一些似乎与此相关的问题,尽管这还不够严重,值得参考。 I can't test this on an ATi card (I haven't seen one in like ten years :D).我无法在 ATi 卡上进行测试(我已经有十年没见过了:D)。

So it seems like your options are:所以看起来你的选择是:

  • Make sure your textures are big enough to never be allocated on the "shared" heaps.确保您的纹理足够大,永远不会在“共享”堆上分配。 This allows the memory leak to proceed much slower - although it's still unreleased memory, it's not going to cause memory fragmentation anywhere near as serious as you're experiencing.这允许内存泄漏进行得更慢 - 尽管它仍然是未释放的内存,但它不会像您遇到的那样严重地导致内存碎片。 You're talking about drawing tiles - this has historically been done with tilesets, which give you a lot better handling (though they also have drawbacks).您在谈论绘制图块 - 这在历史上是通过图块集完成的,它可以为您提供更好的处理(尽管它们也有缺点)。 In my tests, simply avoiding tiny textures all but eliminated the problem - it's hard to tell if it's just hidden or completely gone (both are quite possible).在我的测试中,简单地避免微小的纹理几乎消除了问题 - 很难判断它是隐藏了还是完全消失了(两者都有可能)。
  • Handle your processing in a separate process.在单独的过程中处理您的处理。 Your main application would launch the other process whenever needed, and the memory will be properly reclaimed when the helper process exits.您的主应用程序将在需要时启动另一个进程,并且在辅助进程退出时将正确回收内存。 Of course, this only makes sense if you're writing some processing application - if you're making something that actually displays the textures, this isn't going to help (or at least it's going to be really tricky to setup).当然,这仅在您编写一些处理应用程序时才有意义 - 如果您正在制作实际显示纹理的东西,这不会有帮助(或者至少设置起来非常棘手)。
  • Do not dispose of the textures.不要处理纹理。 Managed texture pool handles paging the texture to and from the device for you, and it even allows you to use priorities etc., as well as flushing the whole on-device (managed) memory. Managed纹理池为您处理与设备之间的纹理分页,它甚至允许您使用优先级等,以及刷新整个设备上(托管)内存。 This means that the textures will remain in your process memory, but it seems that you'll still get better memory usage than with your current approach :)这意味着纹理将保留在您的进程内存中,但与当前方法相比,您似乎仍然会获得更好的内存使用:)
  • Possibly, the problems might be related to eg DirectX 9 contexts only.可能这些问题可能仅与 DirectX 9 上下文有关。 You might want to test with one of the newer interfaces, like DX10 or DXGI.您可能想使用较新的接口之一进行测试,例如 DX10 或 DXGI。 This doesn't necessarily limit you to DX10+ GPUs - but you will lose support for Windows XP (which isn't supported anymore anyway).这不一定会限制您使用 DX10+ GPU - 但您将失去对 Windows XP 的支持(无论如何不再受支持)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM