2015-07-02 14 views
2

Я работаю над приложением .NET 3.5, которое использует SharpDX для рендеринга черепичных 2D-изображений.Осколок памяти SharpDX

Текстуры (Texture2D) загружаются в кеш по требованию и создаются в управляемом пуле.

Текстуры удаляются, когда больше не требуется, и я проверил, что Dispose() вызывается правильно. Отслеживание объектов SharpDX указывает, что текстуры не завершены.

Проблема заключается в том, что большое количество неуправляемой памяти кучи, используемой текстурами, продолжает сохраняться после удаления. Эта память используется повторно при загрузке новой текстуры, поэтому память не просачивается.

Однако в другой части приложения также требуются значительные куски памяти для обработки новых изображений. Поскольку эти кучи все еще присутствуют, хотя текстуры были расположены, недостаточно загруженной памяти для загрузки другого изображения (может быть сотни МБ).

Если я выделил неуправляемый мясник, используя AllocHGlobal, итоговая память кучи полностью исчезнет после звонка FreeHGlobal.

Fragmentation

VMMap показывает неуправляемую кучу (красный) после интенсивного использования приложения.

Unmanaged Heap

Мы видим здесь, что неуправляемые счета кучи для ~ 380MB, даже если только ~ 20MB действительно совершается в этой точке.

Долгосрочное приложение переносится на 64-разрядный. Однако это не является тривиальным из-за неуправляемых зависимостей. Кроме того, не все пользователи находятся на 64-битных машинах.

EDIT: Я собрал демонстрацию проблемы - создайте приложение WinForms и установите SharpDX 2.6.3 через Nuget.

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, 0, 1, Usage.None, Format.Unknown, Pool.Managed, Filter.Point, Filter.None, 0);' Где поток в настоящее время всегда MemoryStream. –

+0

Я добавил упрощенный образец кода, который воспроизводит проблему. –

ответ

1

Кажется, что память, выделяющая проблемы, выделяется драйвером nVidia. Насколько я могу судить, все методы удаления используются правильно, так что это может быть ошибкой в ​​драйверах. Оглядываясь по интернету, есть некоторые проблемы, которые, похоже, связаны с этим, хотя это не имеет ничего серьезного, чтобы стоить ссылки. Я не могу протестировать это на карте ATi (я не видел один за десять лет: D).

Так что, похоже, как ваши варианты:

  • Убедитесь, что ваши текстуры являются достаточно большими, чтобы никогда не быть выделены на «общие» кучах.Это позволяет протечка памяти протекать намного медленнее - хотя это еще неизданная память, это не приведет к фрагментации памяти в любом месте, столь же серьезном, как вы переживаете. Вы говорите о рисовании плиток - это исторически было сделано с помощью наборов тайлов, которые дают вам намного лучшую обработку (хотя у них также есть недостатки). В моих тестах, просто избегая крошечных текстур, все устраняли проблему - трудно сказать, просто ли она скрыта или полностью ушла (оба вполне возможны).
  • Обрабатывайте свою обработку в отдельном процессе. Ваше основное приложение запускает другой процесс, когда это необходимо, и память будет правильно восстановлена, когда вспомогательный процесс завершится. Конечно, это имеет смысл только в том случае, если вы пишете какое-то приложение для обработки - если вы делаете что-то, что на самом деле отображает текстуры, это не поможет (или, по крайней мере, будет очень сложно настроить).
  • Не удаляйте текстуры. Managed пул текстур обрабатывает подкачку текстуры на устройство и из него, и он даже позволяет вам использовать приоритеты и т. Д., А также очищать всю управляемую память. Это означает, что текстуры останутся в вашей памяти процесса, но кажется, что вы по-прежнему будете получать больше памяти, чем с вашим текущим подходом.
  • Возможно, проблемы могут быть связаны с, например, проблемами. Только контексты DirectX 9. Вы можете протестировать один из новых интерфейсов, например DX10 или DXGI. Это не обязательно ограничивает вас графическими процессорами DX10 +, но вы потеряете поддержку Windows XP (которая в любом случае не поддерживается).
+0

Я не уверен, что вы использовали, чтобы прийти к выводу драйвера nVidia, но DebugDiag дает nvd3dum! QueryOglResource дает 97% вероятность утечки, поэтому я думаю, что вы, вероятно, правы. У меня нет карты ATI, но я увижу, что я могу найти. –

+0

Кроме того, я протестировал текстуры 512x512, в конце, как вы сказали, осталось немного кучи. Большое вам спасибо за вашу помощь, это было очень воспитательно! –

+1

@BenOwen Было очень весело. Хотя это разочаровывает это, в конце концов, ответ по-прежнему выглядит «Слишком плохо, он сломан. Попытайтесь избежать острых краев». : D – Luaan