[英]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:所以看起来你的选择是:
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 :)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.