2015-07-02 115 views
2

我正在使用SharpDX渲染平鋪的2D圖像的.NET 3.5應用程序。SharpDX內存碎片

紋理(Texture2D)按需加載到緩存中,並在管理的池中創建。

紋理在不再需要時被處置,並且我已驗證Dispose()被正確調用。 SharpDX對象跟蹤表明沒有紋理正在定型。

問題在於處理後紋理使用的大量非託管堆內存繼續保留。加載新紋理時會重用此內存,所以內存不會泄漏。

但是,應用程序的另一部分還需要大量的內存來處理新圖像。由於這些堆仍然存在,即使紋理已經處理完畢,也沒有足夠的連續內存來加載另一個圖像(可能爲幾百MB)。

如果我使用AllocHGlobal分配未管理的存儲,產生的 堆內存在調用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; 
      } 
     } 
    } 
} 

我的問題,因此是 - (怎麼)我可以回收利用後的這些非託管堆消耗的內存紋理已經處理?

+0

你是如何加載紋理? – Luaan

+0

'Texture texture = Texture.FromStream(device,stream,0,1,0,1, Usage.None,Format.Unknown,Pool.Managed,Filter.Point,Filter.None,0);' 其中stream是當前總是一個MemoryStream。 –

+0

我已經添加了一個簡化的代碼示例,它重現了這個問題。 –

回答

1

看來,提供問題的內存是由nVidia驅動程序分配的。據我所知,所有的解除分配方法都被正確調用,所以這可能是驅動程序中的一個錯誤。環顧互聯網顯示了一些與此相關的問題,儘管它沒有足夠的重要性值得參考。我不能在ATi卡上測試這個(我在十年內沒有看到過:D)。

所以它看起來像你的選擇是:

  • 確保您的紋理足夠大,就不再需要在「共享」堆分配。這可以讓內存泄漏進行得更慢 - 雖然它仍然是未釋放的內存,但它不會導致內存碎片與您遇到的嚴重程度相近。你在談論繪圖瓦 - 這在歷史上是用瓦片集完成的,這給你更好的處理(儘管它們也有缺點)。在我的測試中,簡單地避免微小的紋理,除了消除這個問題 - 很難說它是否隱藏或完全消失(兩者都很可能)。
  • 在一個單獨的過程中處理您的處理。您的主應用程序會在需要時啓動其他進程,並且當輔助進程退出時,內存將被正確回收。當然,這隻有在編寫一些處理應用程序時纔有意義 - 如果您製作的是實際顯示紋理的內容,這不會起到幫助作用(或者至少安裝起來會非常棘手)。
  • 不要處理紋理。 Managed紋理池處理可以爲設備分配紋理,也可以使用優先級等,也可以刷新整個設備(託管)的內存。這意味着紋理將保留在您的進程內存中,但看起來您的內存使用情況似乎仍超出您目前的方法:)
  • 可能的問題可能與例如僅限DirectX 9上下文。您可能想要使用其中一個較新的接口進行測試,如DX10或DXGI。這並不一定會限制您使用DX10 + GPU,但您將失去對Windows XP的支持(無論如何都不再支持)。
+0

我不確定你用什麼來得到nVidia驅動程序的結論 - 但DebugDiag給nvd3dum!QueryOglResource有97%的機會泄漏,所以我認爲你可能是對的。我沒有ATI卡,但會看到我能找到的東西。 –

+0

另外,我測試了512x512紋理,最後只剩下很少的堆,就像你說的那樣。非常感謝您的幫助,這非常有教育意義! –

+1

@BenOwen這很有趣。雖然最終令人沮喪的是,答案似乎仍然是「太糟糕了,它已經壞了,儘量避免鋒利的邊緣。」 :D – Luaan