使用共享数组池后内存暴涨,如何优化ImDrawData的CmdLists实现?
问题分析与修复方案
原实现的核心问题
你的数组池用法完全错误,直接导致内存飙升:
- 每次访问属性都反复归还/租赁数组:这不仅没复用数组,反而让数组池频繁分配、回收,甚至会因为池返回的数组尺寸总是大于等于请求值,长期积累导致内存占用居高不下。
- 未清理数组内的旧实例:归还数组前没清空里面的
ImDrawList引用,这些实例会被数组池持有,无法被GC回收,直接造成内存泄漏。 - 暴露池数组给外部:直接返回租赁的数组,外部调用者如果修改数组内容,会破坏池内数组的状态,后续复用会引发未知问题。
正确的数组池实现
partial class ImDrawData { private ImDrawList[]? _cachedCmdLists; private readonly ArrayPool<ImDrawList> _arrayPool = ArrayPool<ImDrawList>.Shared; public unsafe IReadOnlyList<ImDrawList> CmdLists { get { int currentSize = CmdListsCount; // 只有当缓存数组尺寸不足时,才重新租赁 if (_cachedCmdLists == null || _cachedCmdLists.Length < currentSize) { // 先清理旧数组的实例并归还 if (_cachedCmdLists != null) { // 清空数组内的引用,让GC能回收旧ImDrawList实例 Array.Clear(_cachedCmdLists, 0, _cachedCmdLists.Length); _arrayPool.Return(_cachedCmdLists); } // 租赁新数组 _cachedCmdLists = _arrayPool.Rent(currentSize); } var data = (IntPtr*)((__Internal*)__Instance)->CmdLists; for (int i = 0; i < currentSize; i++) { // 先释放旧实例(如果ImDrawList实现了IDisposable) _cachedCmdLists[i]?.Dispose(); _cachedCmdLists[i] = ImDrawList.__CreateInstance(data[i]); } // 返回只读包装,避免外部修改池数组 return new ReadOnlySpan<ImDrawList>(_cachedCmdLists, 0, currentSize).ToList().AsReadOnly(); } } // 在ImDrawData销毁时清理缓存数组 protected override void Dispose(bool disposing) { if (disposing && _cachedCmdLists != null) { Array.Clear(_cachedCmdLists, 0, _cachedCmdLists.Length); _arrayPool.Return(_cachedCmdLists); _cachedCmdLists = null; } base.Dispose(disposing); } }
关键优化点说明
- 按需租赁数组:只有当缓存数组的尺寸小于当前需要的
CmdListsCount时,才更换数组,避免不必要的池操作。 - 清理旧实例:归还数组前用
Array.Clear清空引用,或者手动释放ImDrawList实例(如果实现了IDisposable),防止内存泄漏。 - 包装返回结果:返回只读集合,避免外部修改池中的原始数组,保证池的安全性。
- 销毁时清理:在
ImDrawData的Dispose方法中归还数组,确保资源被正确回收。
额外建议
- 如果
CmdListsCount变化不频繁,可以考虑缓存生成的ReadOnlyList,减少重复创建的开销。 - 如果是多线程场景,需要给
_cachedCmdLists加锁(比如lock语句),避免并发访问导致的异常。
内容的提问来源于stack exchange,提问作者aybe
相关产品推荐
相关产品推荐

