Le conseil général est que vous ne devriez pas appeler GC.Collect
à partir de votre code, mais quelles sont les exceptions à cette règle?
Je ne peux penser qu'à quelques cas très spécifiques où il peut être judicieux de forcer un ramassage des ordures.
Un exemple qui me vient à l’esprit est un service qui se réveille à intervalles réguliers, effectue une tâche, puis dort longtemps. Dans ce cas, il peut être judicieux de forcer une collecte pour éviter que le processus bientôt inactif ne conserve plus de mémoire que nécessaire.
Existe-t-il d'autres cas où il est acceptable d'appeler GC.Collect
?
Si vous avez de bonnes raisons de croire qu’un ensemble important d’objets - en particulier ceux que vous soupçonnez d’être des générations 1 et 2 - sont désormais éligibles pour le ramassage des ordures, et que ce serait le bon moment pour le ramasser compte tenu de sa faible performance. .
Un bon exemple de ceci est si vous venez de fermer un grand formulaire. Vous savez que tous les contrôles d'interface utilisateur peuvent maintenant être récupérés et qu'une très courte pause le jour où le formulaire est fermé ne sera probablement pas perceptible par l'utilisateur.
MISE À JOUR 2.7.2018
A partir de .NET 4.5 - il y a GCLatencyMode.LowLatency
et GCLatencyMode.SustainedLowLatency
. Lorsque vous entrez ou quittez l'un de ces modes, il est recommandé de forcer un CPG complet avec GC.Collect(2, GCCollectionMode.Forced)
.
A partir de .NET 4.6 - il existe la méthode GC.TryStartNoGCRegion
(utilisée pour définir la valeur en lecture seule GCLatencyMode.NoGCRegion
). Cela peut, en soi, effectuer une collecte des ordures bloquante complète pour tenter de libérer suffisamment de mémoire, mais étant donné que nous interdisons l'utilisation de la technologie GC pendant un certain temps, je dirais que c'est également une bonne idée d'effectuer une analyse GC complète avant et après.
Source: Microsoft Watson, ingénieur chez Microsoft: Writing High-Performance Code .NET, 2nd Ed. 2018.
Voir:
J'utilise GC.Collect
uniquement lors de l'écriture de bancs d'essai bruts de performance/profileur; c'est-à-dire que j'ai deux (ou plus) blocs de code à tester - quelque chose comme:
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestA(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestB(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
...
Donc, TestA()
et TestB()
fonctionnent avec un état aussi similaire que possible - c’est-à-dire que TestB()
ne soit pas martelé simplement parce que TestA
l’a laissé très près du point de basculement.
Un exemple classique serait un simple exe de console (une méthode Main
suffisante pour être postée ici, par exemple), qui montre la différence entre la concaténation de chaînes en boucle et StringBuilder
.
Si j'ai besoin de quelque chose de précis, il s'agira de deux tests complètement indépendants - mais cela suffit souvent si nous voulons simplement minimiser (ou normaliser) le GC pendant les tests pour avoir une idée approximative du comportement.
Pendant le code de production? Je ne l'ai pas encore utilisé ;-p
_ {La meilleure pratique consiste à ne pas forcer la collecte des ordures dans la plupart des cas. _ (Tous les systèmes sur lesquels j'ai travaillé avaient des collectes forcées, posaient des problèmes soulignés. et a grandement accéléré le système.)
Il y a (quelques cas) lorsque vous en savez plus sur l'utilisation de la mémoire que le garbage collector en a. Il est peu probable que cela soit vrai dans une application multi-utilisateur ou dans un service qui répond à plusieurs demandes à la fois.
Cependant, dans certains traitements de type par lots}, vous en savez plus que le CPG. Par exemple. considérez une application qui.
Vous pouvez être en mesure de justifier (après avoir soigneusement vérifié) que vous devez forcer le ramassage complet des ordures après avoir traité chaque fichier.
Un autre cas est un service qui se réveille toutes les quelques minutes pour traiter certains éléments et ne conserve aucun état tant qu’il est endormi. Forcer une collection complète juste avant d'aller dormir peut être utile.
La seule fois que je considérerais forcer une collection est quand je sais que beaucoup d'objet avait été créé récemment et très peu d'objets sont actuellement référencé.
Je préférerais avoir une API de récupération de place lorsque je pourrais lui donner des indices sur ce type de choses sans avoir à forcer un GC moi-même.
Voir aussi " Tidbits Performance de Rico Mariani "
Dans certains cas, vous essayez d’utiliser un code de test utilisant WeakReference .
Dans les grands systèmes 24/7 ou 24/6 - systèmes réagissant aux messages, demandes RPC ou interrogeant une base de données ou un processus en continu -, il est utile de disposer d'un moyen d'identifier les fuites de mémoire. Pour cela, j'ai tendance à ajouter un mécanisme à l'application pour suspendre temporairement tout traitement, puis effectuer un nettoyage complet. Cela met le système dans un état de repos où la mémoire restante est une mémoire légitimement vivante (caches, configuration, etc.) ou est «fuyée» (les objets qui ne sont pas censés être enracinés mais qui ne le sont pas).
Grâce à ce mécanisme, il est beaucoup plus facile de profiler l'utilisation de la mémoire, car les rapports ne seront pas assombris par le bruit généré par le traitement actif.
Pour être sûr d'avoir tous les déchets, vous devez effectuer deux collectes:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
En tant que première collection, tous les objets avec les finaliseurs seront finalisés (mais ils ne seront pas réellement récupérés par les ordures). Le second GC va ramasser ces objets finalisés.
Vous pouvez appeler GC.Collect () lorsque vous savez quelque chose sur la nature de l'application que le garbage collector ne connaît pas. C'est tentant de penser que, en tant qu'auteur, c'est très probable. Cependant, la vérité est que le GC constitue un système expert assez bien écrit et testé, et il est rare que vous sachiez quelque chose sur les chemins de code de bas niveau qu'il ne connaît pas.
Le meilleur exemple auquel je puisse penser où vous pourriez avoir quelques informations supplémentaires est une application qui alterne entre les périodes d'inactivité et les périodes de pointe. Vous souhaitez obtenir les meilleures performances possibles pendant les périodes de pointe et souhaitez par conséquent utiliser le temps d'inactivité pour effectuer des opérations de nettoyage.
Cependant, la plupart du temps, le GC est assez intelligent pour le faire.
Consultez cet article de Rico Mariani. Il donne deux règles pour appeler GC.Collect (la règle 1 est: "Ne pas"):
En tant que solution de fragmentation de la mémoire. Je me débarrassais des exceptions de mémoire lors de l'écriture de nombreuses données dans un flux de mémoire (lecture d'un flux de réseau) Les données ont été écrites en morceaux de 8K. Après avoir atteint 128 M, il y avait une exception, même s'il y avait beaucoup de mémoire disponible (mais elle était fragmentée). L'appel de GC.Collect () a résolu le problème. J'ai été capable de gérer plus d'1G après le correctif.
Je faisais des tests de performance sur array et list:
private static int count = 100000000;
private static List<int> GetSomeNumbers_List_int()
{
var lstNumbers = new List<int>();
for(var i = 1; i <= count; i++)
{
lstNumbers.Add(i);
}
return lstNumbers;
}
private static int[] GetSomeNumbers_Array()
{
var lstNumbers = new int[count];
for (var i = 1; i <= count; i++)
{
lstNumbers[i-1] = i + 1;
}
return lstNumbers;
}
private static int[] GetSomeNumbers_Enumerable_Range()
{
return Enumerable.Range(1, count).ToArray();
}
static void performance_100_Million()
{
var sw = new Stopwatch();
sw.Start();
var numbers1 = GetSomeNumbers_List_int();
sw.Stop();
//numbers1 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"List<int>\" took {0} milliseconds", sw.ElapsedMilliseconds));
sw.Reset();
sw.Start();
var numbers2 = GetSomeNumbers_Array();
sw.Stop();
//numbers2 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"int[]\" took {0} milliseconds", sw.ElapsedMilliseconds));
sw.Reset();
sw.Start();
//getting System.OutOfMemoryException in GetSomeNumbers_Enumerable_Range method
var numbers3 = GetSomeNumbers_Enumerable_Range();
sw.Stop();
//numbers3 = null;
//GC.Collect();
Console.WriteLine(String.Format("\"int[]\" Enumerable.Range took {0} milliseconds", sw.ElapsedMilliseconds));
}
et j'ai OutOfMemoryException
dans la méthode GetSomeNumbers_Enumerable_Range, la seule solution de contournement consiste à libérer la mémoire de la façon suivante:
numbers = null;
GC.Collect();
Dans votre exemple, je pense que le fait d'appeler GC.Collect n'est pas le problème, mais plutôt un problème de conception.
Si vous allez vous réveiller à intervalles réguliers (heure fixe), votre programme doit alors être conçu pour une seule exécution (effectuer la tâche une fois), puis se terminer. Ensuite, vous configurez le programme en tant que tâche planifiée à exécuter à des intervalles planifiés.
De cette façon, vous n'avez pas à vous préoccuper d'appeler GC.Collect (ce que vous devriez rarement si jamais, devez faire).
Ceci étant dit, Rico Mariani a publié un excellent billet de blog sur ce sujet, que vous pouvez trouver ici:
Il est presque nécessaire d'appeler GC.Collect () lors de l'automatisation de Microsoft Office via Interop. Les objets COM pour Office n'aiment pas être libérés automatiquement et peuvent entraîner une très grande quantité de mémoire pour les instances du produit Office. Je ne suis pas sûr que ce soit un problème ou par conception. Il y a beaucoup de messages sur Internet à ce sujet, je ne vais donc pas entrer dans les détails.
Lors de la programmation avec Interop, chaque objet COM doit être libéré manuellement, généralement avec Marshal.ReleseComObject (). De plus, appeler manuellement Garbage Collection peut aider à "nettoyer" un peu. Appeler le code suivant lorsque vous avez terminé avec les objets Interop semble aider beaucoup:
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
D'après mon expérience personnelle, l'utilisation d'une combinaison de ReleaseComObject et d'un appel manuel de garbage collection considérablement réduit l'utilisation de la mémoire par les produits Office, notamment Excel.
Un endroit utile pour appeler GC.Collect () est un test unitaire lorsque vous voulez vérifier que vous ne créez pas de fuite de mémoire (par exemple, si vous faites quelque chose avec WeakReferences ou ConditionalWeakTable, un code généré dynamiquement, etc.).
Par exemple, j'ai quelques tests comme:
WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);
On pourrait faire valoir que l'utilisation de WeakReferences est un problème en soi, mais il semble que si vous créez un système qui repose sur un tel comportement, appeler GC.Collect () est un bon moyen de vérifier ce code.
je ne suis toujours pas sûr de cela… .. Je travaille depuis 7 ans sur un serveur d'applications. Nos plus grandes installations utilisent 24 Go de RAM. Son très haut multithread, et TOUS les appels pour GC.Collect () ont rencontré des problèmes de performances vraiment terribles.
De nombreux composants tiers utilisaient GC.Collect () quand ils pensaient que c'était astucieux de le faire maintenant ..__ Donc, un simple groupe de rapports Excel bloquait le serveur d'applications pour tous les threads plusieurs fois par minute.
Nous avons dû refactoriser tous les composants tiers afin de supprimer les appels GC.Collect (), et tout a bien fonctionné après cela.
Mais j’exécute également des serveurs sur Win32, et j’ai commencé à utiliser intensivement GC.Collect () après avoir obtenu une exception OutOfMemoryException.
Mais je suis également assez incertain à ce sujet, car j’ai souvent remarqué que, lorsque j’obtiens un MOO sur 32 bits et que je réessayais d’exécuter à nouveau la même opération, sans appeler GC.Collect (), cela fonctionnait parfaitement.
Une chose que je me demande, c’est l’exception de MOO elle-même ……. Si j’avais écrit le .Net Framework et que je ne peux pas allouer de bloc de mémoire, j’utiliserais GC.Collect (), défragmenter la mémoire (??) , essayez à nouveau, et si je n'arrive toujours pas à trouver un bloc de mémoire libre, alors je jetterai le MOO-Exception.
Ou du moins, faites de ce comportement une option configurable, en raison des inconvénients du problème de performances avec GC.Collect.
Maintenant, j'ai beaucoup de code comme celui-ci dans mon application pour "résoudre" le problème:
public static TResult ExecuteOOMAware<T1, T2, TResult>(Func<T1,T2 ,TResult> func, T1 a1, T2 a2)
{
int oomCounter = 0;
int maxOOMRetries = 10;
do
{
try
{
return func(a1, a2);
}
catch (OutOfMemoryException)
{
oomCounter++;
if (maxOOMRetries > 10)
{
throw;
}
else
{
Log.Info("OutOfMemory-Exception caught, Trying to fix. Counter: " + oomCounter.ToString());
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(oomCounter * 10));
GC.Collect();
}
}
} while (oomCounter < maxOOMRetries);
// never gets hitted.
return default(TResult);
}
(Notez que le comportement Thread.Sleep () est vraiment un comportement spécifique à une application, car nous exécutons un service de mise en cache ORM et que le service met un certain temps à libérer tous les objets mis en cache, si RAM dépasse certaines valeurs prédéfinies. il attend donc quelques secondes la première fois et augmente le temps d’attente à chaque occurrence de MOO.)
using(var stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Png);
techObject.Last().Image = Image.FromStream(stream);
bitmap.Dispose();
// Without this code, I had an OutOfMemory exception.
GC.Collect();
GC.WaitForPendingFinalizers();
//
}
Il y a des situations où il vaut mieux prévenir que guérir.
Voici une situation.
Il est possible de créer un non géré DLL en C # en utilisant IL rewrite (car il existe des situations où cela est nécessaire).
Supposons maintenant, par exemple, que DLL crée un tableau d'octets au niveau de la classe - car de nombreuses fonctions exportées doivent y accéder. Que se passe-t-il lorsque la DLL est déchargée? Est-ce que le ramasse-miettes est appelé automatiquement à ce stade? Je ne sais pas, mais étant un non géré DLL, il est tout à fait possible que le GC ne soit pas appelé. Et ce serait un gros problème si on ne l'appelait pas. Lorsque la DLL est déchargée, le ramasse-miettes également - qui sera responsable de la collecte des éventuelles ordures et comment le feraient-ils? Mieux vaut utiliser le ramasse-miettes de C #. Avoir une fonction de nettoyage (disponible pour le client DLL) où les variables de niveau classe sont définies sur null et le garbage collector appelé.
Mieux vaut prévenir que guérir.
L'entrée du blog de Scott Holden indiquant quand (et quand ne pas) appeler GC.Collect est spécifique au .NET Compact Framework , mais les règles s'appliquent généralement à tout développement géré.
La réponse courte est: jamais!
Vous devriez essayer d'éviter d'utiliser GC.Collect () car c'est très coûteux. Voici un exemple:
public void ClearFrame(ulong timeStamp)
{
if (RecordSet.Count <= 0) return;
if (Limit == false)
{
var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
if (seconds <= _preFramesTime) return;
Limit = true;
do
{
RecordSet.Remove(RecordSet[0]);
} while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
}
else
{
RecordSet.Remove(RecordSet[0]);
}
GC.Collect(); // AVOID
}
RESULTAT DU TEST: UTILISATION DE LA CPU 12%
Quand vous changez ceci:
public void ClearFrame(ulong timeStamp)
{
if (RecordSet.Count <= 0) return;
if (Limit == false)
{
var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
if (seconds <= _preFramesTime) return;
Limit = true;
do
{
RecordSet[0].Dispose(); // Bitmap destroyed!
RecordSet.Remove(RecordSet[0]);
} while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
}
else
{
RecordSet[0].Dispose(); // Bitmap destroyed!
RecordSet.Remove(RecordSet[0]);
}
//GC.Collect();
}
RESULTAT DU TEST: UTILISATION DE LA CPU 2-3%
Ce n'est pas pertinent pour la question, mais pour les transformations XSLT en .NET (XSLCompiledTranform), vous n'aurez peut-être pas le choix. Un autre candidat est le contrôle MSHTML.
Puisqu'il y a Small Object Heap (SOH) et Large Object Heap (LOH)
Nous pouvons appeler GC.Collect () pour supprimer un objet de référence dans la SOP et déplacer un objet vécu à la génération suivante.
Dans .net4.5, nous pouvons également compacter LOH en utilisant largeobjectheapcompactionmode
Si vous utilisez une version de .net inférieure à 4.5, la collecte manuelle peut être inévitable (surtout si vous avez affaire à de nombreux «gros objets»).
ce lien décrit pourquoi:
https://blogs.msdn.Microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/