SharpDX内存碎片
SharpDX memory fragmentation
我正在开发一个 .NET 3.5 应用程序,它使用 SharpDX 渲染平铺的 2D 图像。
纹理 (Texture2D) 按需加载到缓存中,并在托管池中创建。
纹理在不再需要时被丢弃,并且我已验证正确调用了 Dispose()。 SharpDX 对象跟踪表明没有纹理正在完成。
问题是纹理使用的大量非托管堆内存在处理后继续保留。加载新纹理时会重复使用此内存,因此不会泄漏内存。
但是,应用程序的另一部分也需要大量内存来处理新图像。因为这些堆仍然存在,即使纹理已被处理,也没有足够的连续内存来加载另一个图像(可能是数百 MB)。
如果我使用 AllocHGlobal
分配非托管内存,结果
调用 FreeHGlobal
.
后堆内存再次完全消失
VMMap 在大量使用应用程序后显示非托管堆(红色)。
我们可以在这里看到非托管堆占 ~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;
}
}
}
}
因此,我的问题是 - (如何)在处理纹理后回收这些非托管堆消耗的内存?
出现问题的内存似乎是由 nVidia 驱动程序分配的。据我所知,所有的释放方法都被正确调用,所以这可能是驱动程序中的一个错误。环顾互联网显示了一些似乎与此相关的问题,尽管还没有严重到值得参考的程度。我无法在 ATi 卡上对此进行测试(我已经有 10 年没见过了 :D)。
看来你的选择是:
- 确保您的纹理足够大,永远不会分配到 "shared" 堆上。这允许内存泄漏进行得更慢 - 尽管它仍然是未释放的内存,但它不会导致内存碎片接近您遇到的严重程度。你在谈论绘制图块——这在历史上是用图块集完成的,它给你更好的处理(尽管它们也有缺点)。在我的测试中,简单地避免微小的纹理几乎消除了这个问题——很难判断它是只是隐藏了还是完全消失了(两者都是很有可能的)。
- 在单独的进程中处理您的处理。您的主应用程序会在需要时启动其他进程,并且当辅助进程退出时,内存将被正确回收。当然,这只有在您正在编写一些处理应用程序时才有意义 - 如果您正在制作实际显示纹理的东西,这将无济于事(或者至少设置起来非常棘手)。
- 请勿丢弃纹理。
Managed
纹理池为您处理纹理与设备之间的分页,它甚至允许您使用优先级等,以及刷新整个设备上(托管)内存。这意味着纹理将保留在您的进程内存中,但与当前方法相比,您似乎仍会获得更好的内存使用率:)
- 可能,这些问题可能与例如仅限 DirectX 9 上下文。您可能想要使用较新的接口之一进行测试,例如 DX10 或 DXGI。这不一定会限制您使用 DX10+ GPU - 但您将失去对 Windows XP 的支持(无论如何都不再支持)。
我正在开发一个 .NET 3.5 应用程序,它使用 SharpDX 渲染平铺的 2D 图像。
纹理 (Texture2D) 按需加载到缓存中,并在托管池中创建。
纹理在不再需要时被丢弃,并且我已验证正确调用了 Dispose()。 SharpDX 对象跟踪表明没有纹理正在完成。
问题是纹理使用的大量非托管堆内存在处理后继续保留。加载新纹理时会重复使用此内存,因此不会泄漏内存。
但是,应用程序的另一部分也需要大量内存来处理新图像。因为这些堆仍然存在,即使纹理已被处理,也没有足够的连续内存来加载另一个图像(可能是数百 MB)。
如果我使用 AllocHGlobal
分配非托管内存,结果
调用 FreeHGlobal
.
VMMap 在大量使用应用程序后显示非托管堆(红色)。
我们可以在这里看到非托管堆占 ~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;
}
}
}
}
因此,我的问题是 - (如何)在处理纹理后回收这些非托管堆消耗的内存?
出现问题的内存似乎是由 nVidia 驱动程序分配的。据我所知,所有的释放方法都被正确调用,所以这可能是驱动程序中的一个错误。环顾互联网显示了一些似乎与此相关的问题,尽管还没有严重到值得参考的程度。我无法在 ATi 卡上对此进行测试(我已经有 10 年没见过了 :D)。
看来你的选择是:
- 确保您的纹理足够大,永远不会分配到 "shared" 堆上。这允许内存泄漏进行得更慢 - 尽管它仍然是未释放的内存,但它不会导致内存碎片接近您遇到的严重程度。你在谈论绘制图块——这在历史上是用图块集完成的,它给你更好的处理(尽管它们也有缺点)。在我的测试中,简单地避免微小的纹理几乎消除了这个问题——很难判断它是只是隐藏了还是完全消失了(两者都是很有可能的)。
- 在单独的进程中处理您的处理。您的主应用程序会在需要时启动其他进程,并且当辅助进程退出时,内存将被正确回收。当然,这只有在您正在编写一些处理应用程序时才有意义 - 如果您正在制作实际显示纹理的东西,这将无济于事(或者至少设置起来非常棘手)。
- 请勿丢弃纹理。
Managed
纹理池为您处理纹理与设备之间的分页,它甚至允许您使用优先级等,以及刷新整个设备上(托管)内存。这意味着纹理将保留在您的进程内存中,但与当前方法相比,您似乎仍会获得更好的内存使用率:) - 可能,这些问题可能与例如仅限 DirectX 9 上下文。您可能想要使用较新的接口之一进行测试,例如 DX10 或 DXGI。这不一定会限制您使用 DX10+ GPU - 但您将失去对 Windows XP 的支持(无论如何都不再支持)。