Comment puis-je récupérer les fichiers .NET de manière agressive?

J’ai une application qui est utilisée dans le traitement de l’image, et je trouve que j’alloue généralement des tableaux de la taille de 4000×4000, ainsi que des flottants occasionnels et autres. Actuellement, le framework .NET a tendance à se bloquer dans cette application apparemment aléatoirement, presque toujours avec une erreur de mémoire insuffisante. 32mb n’est pas une déclaration énorme, mais si .NET fragmente la mémoire, il est fort possible que de telles allocations continues ne se comportent pas comme prévu.

Est-il possible de demander au ramasse-miettes d’être plus agressif ou de défragmenter la mémoire (si c’est le problème)? Je me rends compte qu’il y a les appels GC.Collect et GC.WaitForPendingFinalizers, et je les ai arrosés assez généreusement à travers mon code, mais je reçois toujours les erreurs. C’est peut-être parce que j’appelle beaucoup les routines DLL, mais je n’en suis pas certain. Je suis passé par-dessus ce code C ++ et assurez-vous que toute mémoire que je déclare avoir supprimée, mais je reçois toujours ces accidents C #, donc je suis sûr que ce n’est pas là. Je me demande si les appels C ++ pourraient interférer avec le GC, le faisant quitter la mémoire car il interagissait auparavant avec un appel natif – est-ce possible? Si oui, puis-je désactiver cette fonctionnalité?

EDIT: Voici un code très spécifique qui provoquera le crash. Selon cette question , je n’ai pas besoin de disposer des objects BitmapSource ici. Voici la version naïve, pas de GC.Collecte dedans. Il se bloque généralement à l’itération 4 à 10 de la procédure d’annulation. Ce code remplace le constructeur dans un projet WPF vide, car j’utilise WPF. Je fais le wackiness avec la source de bitmaps à cause des limitations que j’ai expliquées dans ma réponse à @dthorpe ci-dessous aussi bien que les conditions énumérées dans cette question de SO .

public partial class Window1 : Window { public Window1() { InitializeComponent(); //Attempts to create an OOM crash //to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops int theRows = 4000, currRows; int theColumns = 4000, currCols; int theMaxChange = 30; int i; List theList = new List();//the list of images in the undo/redo stack byte[] displayBuffer = null;//the buffer used as a bitmap source BitmapSource theSource = null; for (i = 0; i = 0; i--) { displayBuffer = new byte[theList[i].Length]; theSource = BitmapSource.Create((theColumns - i), (theRows - i), 96, 96, PixelFormats.Gray8, null, displayBuffer, ((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8); System.Console.WriteLine("Got to undo change " + i.ToSsortingng()); System.Threading.Thread.Sleep(100); } } } 

Maintenant, si je suis explicite en appelant le ramasse-miettes, je dois envelopper le code entier dans une boucle externe pour provoquer le crash du MOO. Pour moi, cela a tendance à se produire autour de x = 50 ou plus:

 public partial class Window1 : Window { public Window1() { InitializeComponent(); //Attempts to create an OOM crash //to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops for (int x = 0; x < 1000; x++){ int theRows = 4000, currRows; int theColumns = 4000, currCols; int theMaxChange = 30; int i; List theList = new List();//the list of images in the undo/redo stack byte[] displayBuffer = null;//the buffer used as a bitmap source BitmapSource theSource = null; for (i = 0; i = 0; i--) { displayBuffer = new byte[theList[i].Length]; theSource = BitmapSource.Create((theColumns - i), (theRows - i), 96, 96, PixelFormats.Gray8, null, displayBuffer, ((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8); GC.WaitForPendingFinalizers();//force gc to collect, because we're in scenario 2, lots of large random changes GC.Collect(); } System.Console.WriteLine("Got to changelist " + x.ToSsortingng()); System.Threading.Thread.Sleep(100); } } } 

Si je gère mal la mémoire dans l’un ou l’autre scénario, s’il y a quelque chose que je devrais repérer avec un profileur, faites le moi savoir. C’est une routine assez simple ici.

Malheureusement, il semble que la réponse de @ Kevin soit correcte: il s’agit d’un bogue dans .NET et de la manière dont .NET traite les objects de plus de 85 Ko. Cette situation me semble extrêmement étrange; Powerpoint pourrait-il être réécrit en .NET avec ce type de limitation, ou l’une des autres applications de la suite Office? Il ne me semble pas que le 85k soit très volumineux, et je pense également que tout programme utilisant des allocations dites «volumineuses» deviendrait instable en quelques jours ou quelques semaines lors de l’utilisation de .NET.

EDIT : Il semble que Kevin a raison, il s’agit d’une limitation du GC de .NET. Pour ceux qui ne veulent pas suivre l’intégralité du fil, .NET possède quatre tas de GC: gen0, gen1, gen2 et LOH (Large Object Heap). Tout ce qui est 85k ou moins passe sur l’un des trois premiers tas, en fonction du temps de création (déplacé de gen0 à gen1 à gen2, etc.). Les objects de plus de 85k sont placés sur le LOH. Le LOH n’est jamais compacté, de sorte que les allocations du type que je suis en train de faire finiront par provoquer une erreur de MOO lorsque des objects seront dispersés dans cet espace mémoire. Nous avons constaté que passer à .NET 4.0 aide quelque peu le problème, en retardant l’exception, mais sans l’empêcher. Pour être honnête, cela ressemble un peu à la barrière 640k – 85k devrait suffire pour toute application utilisateur (pour paraphraser cette vidéo d’une discussion du GC dans .NET). Pour mémoire, Java ne présente pas ce comportement avec son GC.

Voici quelques articles détaillant les problèmes avec le tas d’objects volumineux. Cela ressemble à ce que vous pourriez rencontrer.

http://connect.microsoft.com/VisualStudio/feedback/details/521147/large-object-heap-fragmentation-causes-outofmemoryexception

Dangers du tas d’objects volumineux:
http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/

Voici un lien sur la façon de collecter des données sur le tas d’objects volumineux (LOH):
http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

Selon cela, il semble qu’il n’y ait aucun moyen de compacter le LOH. Je ne trouve rien de plus récent qui dit explicitement comment le faire, et il semble donc que cela n’a pas changé dans la version 2.0:
http://blogs.msdn.com/maoni/archive/2006/04/18/large-object-heap.aspx

Le moyen le plus simple de résoudre le problème consiste à créer de petits objects, dans la mesure du possible. Votre autre option consiste à ne créer que quelques objects volumineux et à les réutiliser encore et encore. Pas une idée, mais cela pourrait être mieux que de réécrire la structure de l’object. Comme vous avez dit que les objects créés (tableaux) sont de tailles différentes, cela peut être difficile, mais cela pourrait empêcher l’application de tomber en panne.

Commencez par restreindre le problème. Si vous avez une fuite de mémoire native, le fait de lancer le GC ne fera rien pour vous.

Exécutez perfmon et examinez la taille du segment de mémoire .NET et les compteurs d’octets privés. Si la taille du tas rest assez constante mais que les octets privés augmentent, vous avez un problème de code natif et vous devrez sortir les outils C ++ pour le déboguer.

En supposant que le problème est avec le tas .NET, vous devez exécuter un profileur sur le code comme le profileur Ant de Redgate ou DotTrace de JetBrain. Cela vous dira quels objects prennent de la place et ne sont pas collectés rapidement. Vous pouvez également utiliser WinDbg avec SOS pour cela, mais c’est une interface délicate (puissante cependant).

Une fois que vous avez trouvé les éléments incriminés, il devrait être plus évident de les traiter. Certaines des choses qui causent des problèmes sont des champs statiques référençant des objects, des gestionnaires d’évènements non enregistrés, des objects vivant assez longtemps pour entrer dans Gen2 mais mourant peu après, etc. Sans profil du tas de mémoire, vous ne serez pas capable de repérer la réponse.

Quoi que vous fassiez, les appels GC.Collect “saupoudrent généreusement” sont presque toujours la mauvaise façon de résoudre le problème.

Il y a peu de chances que le passage à la version serveur du GC améliore les choses (juste une propriété dans le fichier de configuration) – la version par défaut de la station de travail est conçue pour garder une interface réactive et renonce efficacement aux grandes colonnes longues.

Utilisez Process Explorer (à partir de Sysinternals) pour voir ce qu’est le tas d’objects volumineux pour votre application. Votre meilleur pari va rendre vos tableaux plus petits mais en avoir plus. Si vous pouvez éviter d’allouer vos objects sur le LOH, vous n’obtiendrez pas les exceptions OutOfMemoryException et vous ne devrez pas non plus appeler manuellement GC.Collect.

Le LOH n’est pas compacté et n’alloue que de nouveaux objects à la fin, ce qui signifie que vous pouvez manquer d’espace assez rapidement.

Si vous allouez une grande quantité de mémoire dans une bibliothèque non gérée (c’est-à-dire une mémoire dont le GC n’est pas au courant), vous pouvez en informer le GC avec la méthode GC.AddMemoryPressure .

Bien sûr, cela dépend quelque peu de ce que fait le code non géré. Vous n’avez pas expressément indiqué qu’il allouait de la mémoire, mais j’ai l’impression que c’est le cas. Si c’est le cas, c’est exactement pour quoi cette méthode a été conçue. Là encore, si la bibliothèque non gérée alloue beaucoup de mémoire, il est également possible qu’elle fragmente la mémoire, ce qui échappe totalement au contrôle du GC, même avec AddMemoryPressure . J’espère que ce n’est pas le cas; si c’est le cas, vous devrez probablement refactoriser la bibliothèque ou changer la façon dont elle est utilisée.

PS N’oubliez pas d’appeler GC.RemoveMemoryPressure lorsque vous libérez enfin la mémoire non gérée.

(PPS Certaines des autres réponses sont probablement correctes, il y a beaucoup plus de chances que ce soit simplement une fuite de mémoire dans votre code, surtout si c’est le traitement de l’image, je parie que vous ne IDIsposable pas correctement vos instances IDIsposable . au cas où ces réponses ne vous mèneraient nulle part, vous pouvez prendre un autre chemin.)

Juste un côté: le ramasse-miettes .NET effectue un GC “rapide” lorsqu’une fonction retourne à son appelant. Cela disposera les vars locaux déclarés dans la fonction.

Si vous structurez votre code de telle sorte que vous ayez une grande fonction qui alloue de gros blocs encore et encore dans une boucle, en atsortingbuant chaque nouveau bloc à la même variable locale, le GC risque de ne pas récupérer les blocs non référencés pendant un certain temps.

Si, par contre, vous structurez votre code de telle sorte que vous ayez une fonction externe avec une boucle qui appelle une fonction interne et que la mémoire est allouée et affectée à une variable locale dans cette fonction interne, le GC doit intervenir immédiatement lorsque le la fonction interne retourne à l’appelant et récupère le gros bloc de mémoire qui vient d’être alloué, car il s’agit d’une variable locale dans une fonction renvoyée.

Évitez la tentation de GC.Collect avec GC.Collect explicitement.

En plus de gérer les allocations de manière plus conviviale pour le GC (par exemple, réutiliser des tableaux, etc.), il existe une nouvelle option: vous pouvez provoquer manuellement le compactage du LOH.

 GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; 

Cela provoquera un compactage LOH la prochaine fois qu’une collection gen-2 se GC.Collect (soit seule, soit par votre appel explicite de GC.Collect ).

Notez que le fait de ne pas compacter le LOH est généralement une bonne idée – votre scénario est tout à fait suffisant pour permettre un compactage manuel. Le LOH est généralement utilisé pour les objects volumineux et de longue durée, tels que les tampons pré-alloués qui sont réutilisés au fil du temps, etc.

Si votre version .NET ne le permet pas encore, vous pouvez également essayer d’allouer des tailles de puissances de deux, plutôt que d’affecter précisément la quantité de mémoire dont vous avez besoin. C’est ce que font de nombreux allocateurs natifs pour s’assurer que la fragmentation de la mémoire ne soit pas incroyablement stupide (elle limite fondamentalement la fragmentation maximale du tas). C’est embêtant, mais si vous pouvez limiter le code qui gère cela à une petite partie de votre code, c’est une solution de rechange décente.

Notez que vous devez toujours vous assurer qu’il est possible de compacter le tas – toute mémoire épinglée empêchera le compactage dans le tas dans lequel elle se trouve.

Une autre option utile consiste à utiliser la pagination – ne jamais allouer plus de 64 Ko d’espace contigu sur le tas, par exemple; Cela signifie que vous éviterez d’utiliser entièrement le LOH. Ce n’est pas trop difficile de gérer cela dans un simple “tableau-wrapper” dans votre cas. L’essentiel est de maintenir un bon équilibre entre les exigences de performance et l’abstraction raisonnable.

Et bien sûr, en dernier recours, vous pouvez toujours utiliser du code non sécurisé. Cela vous donne beaucoup de souplesse dans la gestion des allocations de mémoire (bien que cela soit un peu plus pénible que l’utilisation de C ++ par exemple), notamment en vous permettant d’allouer explicitement de la mémoire non gérée, de travailler avec cela et de libérer la mémoire manuellement. Encore une fois, cela n’a de sens que si vous pouvez isoler ce code sur une petite partie de votre base de code totale – et assurez-vous d’avoir un wrapper géré sécurisé pour la mémoire, y compris le finaliseur approprié (pour maintenir un niveau de sécurité décent) . Ce n’est pas trop difficile en C #, mais si vous vous retrouvez trop souvent, il serait peut-être judicieux d’utiliser C ++ / CLI pour ces parties du code et de les appeler à partir de votre code C #.

Avez-vous testé les memory leaks? J’ai utilisé .NET Memory Profiler avec beaucoup de succès sur un projet qui comportait un certain nombre de memory leaks très subtiles et persistantes.

Juste comme vérification de la santé mentale, assurez-vous d’appeler Dispose sur tous les objects qui implémentent IDisposable .

Vous pouvez implémenter votre propre classe de tableau qui divise la mémoire en blocs non contigus. Disons que vous avez un tableau 64 par 64 de [64,64] tableaux courts qui sont alloués et désalloués séparément. Ensuite, mappez simplement sur le bon. Emplacement 66,66 serait à l’emplacement [2,2] dans le tableau à [1,1].

Ensuite, vous devriez pouvoir esquiver le tas de gros objects.

Le problème est probablement dû au nombre de ces gros objects que vous avez en mémoire. La fragmentation serait un problème plus probable si ce sont des tailles variables (alors que cela pourrait toujours poser problème). Vous avez indiqué dans les commentaires que vous stockiez une stack d’annulation en mémoire pour les fichiers image. Si vous déplacez ceci sur le disque, vous économiserez des tonnes d’espace mémoire d’application.

De plus, le fait de déplacer l’annulation sur le disque ne devrait pas avoir un impact négatif sur les performances, car ce n’est pas quelque chose que vous utiliserez tout le temps. (Si cela devient un goulot d’étranglement, vous pouvez toujours créer un système de cache disque / mémoire hybride.)

Élargi…

Si vous êtes vraiment préoccupé par l’impact possible des performances provoquées par le stockage des données d’annulation sur le système de fichiers, vous pouvez considérer que le système de mémoire virtuelle a de bonnes chances de paginer ces données dans votre fichier de page virtuelle. Si vous créez votre propre fichier / espace de swap pour ces fichiers d’annulation, vous aurez la possibilité de contrôler quand et où le disque est appelé. N’oubliez pas, même si nous souhaitons tous que nos ordinateurs disposent de ressources infinies, ils sont très limités.

1,5 Go (espace mémoire de l’application utilisable) / 32 Mo (taille de la demande de mémoire importante) ~ = 46

vous pouvez utiliser cette méthode:

 public static void FlushMemory() { Process prs = Process.GetCurrentProcess(); prs.MinWorkingSet = (IntPtr)(300000); } 

trois façons d’utiliser cette méthode.

1 – après object géré tel que class, ….

2 – créer une timer avec de tels intervalles de 2000.

3 – créer un fil pour appeler cette méthode.

Je vous suggère d’utiliser cette méthode dans le fil ou la timer.

La meilleure façon de le faire est que cet article montre, il est en espagnol, mais vous comprenez le code. http://www.nerdcoder.com/c-net-forzar-liberacion-de-memoria-de-nuestras-aplicaciones/

Voici le code en cas de lien get brock

 using System.Runtime.InteropServices; .... public class anyname { .... [DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)] private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int maximumWorkingSetSize); public static void alzheimer() { GC.Collect(); GC.WaitForPendingFinalizers(); SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1); } 

….

vous appelez alzheimer() pour nettoyer / libérer de la mémoire.

Le GC ne prend pas en compte le tas non géré. Si vous créez beaucoup d’objects qui ne sont que des wrappers en C# vers une mémoire non gérée plus grande, votre mémoire est en train d’être dévorée, mais le GC ne peut pas prendre de décisions rationnelles car il ne voit que le tas géré.

Vous vous retrouvez dans une situation où le GC ne pense pas que vous êtes à court de mémoire parce que la plupart des choses sur votre tas de gen 1 sont des références de 8 octets où, en réalité, elles sont comme des icebergs en mer. La plupart de la mémoire est en dessous!

Vous pouvez utiliser ces appels au GC :

  • System :: GC :: AddMemoryPressure (sizeOfField);
  • System :: GC :: RemoveMemoryPressure (sizeOfField);

Ces méthodes permettent au GC de voir la mémoire non gérée (si vous lui fournissez les chiffres corrects).