Maison  >  Article  >  développement back-end  >  Tutoriel pratique sur l'écriture de .NET hautes performances

Tutoriel pratique sur l'écriture de .NET hautes performances

零下一度
零下一度original
2017-06-25 09:08:431544parcourir

Réduire le taux d'allocation

Cela ne nécessite presque aucune explication. Cela réduit l'utilisation de la mémoire, ce qui réduit naturellement la pression lors du recyclage du GC, et réduit en même temps la fragmentation de la mémoire et l'utilisation du processeur. Il existe des moyens d'y parvenir, mais cela peut entrer en conflit avec d'autres conceptions.

Vous devez examiner attentivement chaque objet au fur et à mesure que vous le concevez et vous demander :

  1. Ai-je vraiment besoin de cet objet ?

  2. Ce champ est-il ce dont j'ai besoin ?

  3. Puis-je réduire la taille du tableau ?

  4. Puis-je réduire la taille des primitives (remplacer Int64 par Int32, etc.) ?

  5. Ces objets sont-ils utilisés uniquement dans de rares cas ou uniquement lors de l'initialisation ?

  6. Est-il possible de convertir certaines classes en structures afin qu'elles puissent être allouées sur la pile ou faire partie d'un objet ?

  7. Est-ce que j'alloue beaucoup de mémoire mais n'en utilise qu'une infime fraction ?

  8. Puis-je obtenir des données pertinentes provenant d'autres endroits ?

Petite histoire : Dans une fonction sur le serveur qui répond aux requêtes, nous avons constaté qu'une mémoire plus grande que le segment de mémoire sera allouée dans une requête. Cela nous amène à déclencher un GC complet pour chaque requête. En effet, le CLR exige que tous les objets de génération 0 soient dans un segment de mémoire. Si le segment de mémoire actuellement alloué est plein, un nouveau segment de mémoire sera ouvert et la mémoire d'origine. Le segment sera ouvert en même temps. Le segment mémoire est recyclé pendant 2 générations. Ce n'est pas une bonne implémentation puisque nous n'avons pas d'autre moyen que de réduire l'allocation de mémoire.

La règle la plus importante

Il existe une règle de base pour la programmation haute performance avec garbage collection, qui est en fait une règle directrice pour la conception de code.

Les objets à collecter sont soit dans la génération 0, soit pas du tout.
Collectez des objets dans la génération 0 ou pas du tout. Le fait est que vous en voulez un. objectez à avoir une durée de vie extrêmement courte et à ne jamais y toucher pendant la GC, ou, si vous ne pouvez pas faire cela, ils devraient passer à 2 générations le plus rapidement possible et y rester pour toujours, ne seront jamais recyclés. Cela signifie que vous conservez pour toujours une référence à un objet à longue durée de vie. Habituellement, cela signifie également que les objets peuvent être réutilisés, en particulier les objets contenus dans de grands tas d'objets. Le recyclage des GC pour chaque génération supérieure prendra plus de temps que la génération précédente. Si vous souhaitez conserver de nombreux objets de génération 0,1 et quelques objets de génération 2. Même si le GC en arrière-plan est activé pour effectuer 2 générations de recyclage, cela consommera beaucoup d'opérations CPU. Vous préférerez peut-être utiliser cette partie du CPU pour l'application au lieu du GC.


Remarque Vous avez peut-être entendu un dicton selon lequel chaque recyclage de 10 générations 0 produira un recyclage de 1 génération, et chaque recyclage de 10 générations 1 produira un recyclage de 2 générations. C'est en fait incorrect, mais vous devez comprendre que vous souhaitez générer autant de collections rapides de génération 0 que possible et un petit nombre de collections de génération 2.

Il vaut mieux éviter le recyclage de génération 1, principalement parce que les objets qui ont été promus de la génération 0 à la génération 1 seront transférés à la génération 2 à ce moment-là. La génération 1 est un tampon pour les objets entrant dans la génération 2.

Idéalement, chaque objet que vous attribuez devrait terminer son cycle de vie avant la prochaine collection génération 0. Vous pouvez mesurer le temps entre les GC et le comparer à la durée de vie des objets de votre application. Des informations sur la manière d’utiliser les outils de mesure des cycles de vie sont disponibles à la fin de ce chapitre.
Vous n'êtes peut-être pas habitué à penser ainsi, mais cette règle concerne tous les aspects de l'application. Vous devez y réfléchir souvent et apporter un changement fondamental dans votre mentalité, afin de pouvoir mettre en œuvre cette règle la plus importante.


Raccourcir le cycle de vie des objets

Plus la portée d'un objet est courte, plus petites sont les chances qu'il soit promu à la génération suivante lors du prochain GC. En général, ne créez pas d'objets avant d'en avoir besoin.

Dans le même temps, lorsque le coût de création d'objets est si élevé, des exceptions peuvent être créées plus tôt afin qu'elles n'interfèrent pas avec d'autres logiques de traitement.

De plus, vous devez également vous assurer que l'objet quitte le champ d'application le plus tôt possible. Pour les variables locales, vous pouvez mettre fin à leur durée de vie après la dernière utilisation, ou même avant la fin de la méthode. Vous pouvez joindre le code avec {}, ce qui n'affectera pas votre exécution, mais le compilateur considérera que l'objet dans cette portée a terminé son cycle de vie et n'est plus utilisé. Si vous devez appeler la méthode d'un objet, essayez de réduire l'intervalle de temps entre le premier et le dernier appel afin que le GC puisse recycler l'objet le plus tôt possible.

Si l'objet est associé (référencé) à certains objets qui seront conservés pendant une longue période, vous devez libérer leurs relations de référence. Vous pouvez avoir davantage de vérifications nulles, ce qui peut rendre le code plus complexe. Cela peut également créer une tension entre l'état disponible de l'objet (avoir toujours l'état complet disponible) et l'efficacité, notamment lors du débogage.
Une solution consiste à convertir l'objet à effacer d'une autre manière, comme un message de journal, afin que les informations pertinentes puissent être interrogées lors du débogage ultérieur.
Une autre méthode consiste à ajouter des options configurables au code (sans libérer la relation entre les objets) : exécuter le programme (ou exécuter une partie spécifique du programme, comme une requête spécifique), dans ce mode les objets ne sont pas libérés relation de référence, mais pour garder l'objet aussi pratique que possible pour le débogage.

Réduire la profondeur de la hiérarchie des objets

Comme mentionné au début de ce chapitre, le GC parcourra les relations de référence des objets lors du recyclage. En mode serveur GC, le GC fonctionnera de manière multithread, mais si un thread doit traiter un objet à un niveau approfondi, tous les threads qui l'ont traité devront attendre que ce thread termine le traitement avant de quitter. . Dans les futures versions du CLR, vous n'aurez pas besoin de prêter trop d'attention à ce problème. GC utilisera un meilleur algorithme de marquage pour l'équilibrage de charge lors de l'exécution multithread. Mais si votre niveau d’objet est très profond, vous devez quand même faire attention à ce problème.

Réduire les références entre les objets

Ceci est lié à la profondeur de la section précédente, mais il y a aussi d'autres facteurs.
Si un objet fait référence à de nombreux objets (tableau, Liste), il passera beaucoup de temps à parcourir les objets. C'est le GC qui pose problème depuis longtemps car il possède un graphe de relations complexe.
Un autre problème est que si vous ne pouvez pas déterminer facilement le nombre de relations de référence qu'un objet possède, vous ne pouvez pas prédire avec précision le cycle de vie de l'objet. Réduire cette complexité est tout à fait nécessaire, non seulement pour rendre le code plus robuste, mais également pour faciliter le débogage et obtenir de meilleures performances.
De plus, veuillez noter que les références entre objets de différentes générations entraîneront également une inefficacité du GC, en particulier les références d'anciens objets vers de nouveaux objets. Par exemple, si un objet de 2e génération a une relation de référence dans un objet de 0e génération, alors chaque fois qu'un GC de génération 0 se produit, certains des objets de 2e génération doivent également être analysés pour voir s'ils contiennent toujours des références à l'objet de 0e génération. . Bien qu'il ne s'agisse pas d'un GC complet, cela reste un travail inutile et vous devriez essayer d'éviter cette situation.

Évitez d'épingler des objets (Épinglage)

Épingler des objets peut garantir la sécurité des données transmises du code managé au code natif. Les plus courants sont les tableaux et les chaînes. Si votre code n'a pas besoin d'interagir avec le code natif, ne vous inquiétez pas de sa surcharge en termes de performances.
Épingler un objet signifie que l'objet ne peut pas être déplacé pendant le garbage collection (phase de compression). Bien que l’épinglage d’objets n’entraîne pas beaucoup de surcharge, cela entravera les opérations de recyclage du GC et augmentera le risque de fragmentation de la mémoire. Le GC enregistrera l'emplacement des objets pendant le recyclage afin que l'espace entre eux puisse être utilisé lors de la réallocation. Cependant, s'il y a de nombreux objets épinglés, cela entraînera une augmentation de la fragmentation de la mémoire.
Peg peut être explicite ou implicite. Ce qui est montré est de le définir avec le paramètre GCHandleType.Pinned lors de l'utilisation de GCHandle, ou d'utiliser le mot-clé fixe en mode non sécurisé. La différence entre l'utilisation du mot clé fixe et GCHandle est de savoir si la méthode Dispose sera appelée explicitement. Bien que l'utilisation de fix soit très pratique, elle ne peut pas être utilisée dans des situations asynchrones. Cependant, vous pouvez toujours créer un objet handle (GCHandle), le renvoyer et le traiter lors du rappel.
L'épinglage implicite est plus courant, mais plus difficile à détecter et à supprimer. L'exemple le plus évident est la transmission d'objets à du code non géré via des appels de plateforme (P/Invoke). Il ne s'agit pas seulement de votre code : certaines des API gérées que vous appelez fréquemment appellent également du code natif et des objets PIN.
Le CLR épinglera également certaines de ses propres données, mais c'est généralement quelque chose dont vous n'avez pas besoin de vous soucier.
Idéalement, vous devriez essayer de ne pas épingler des objets autant que possible. Si cela n'est pas possible, suivez les règles importantes précédentes et essayez de libérer ces objets épinglés dès que possible. Si l'objet est simplement épinglé et libéré, il y a peu de chances que cela affecte l'opération de collecte. Vous souhaitez également éviter d’épingler plusieurs objets en même temps. C'est légèrement mieux si les objets épinglés sont échangés vers la génération 2 ou alloués dans LOH. Selon cette règle, vous pouvez allouer un grand tampon sur le tas d'objets volumineux et gérer vous-même le tampon en fonction des besoins réels. Ou allouez des tampons sur des paires de petits objets, puis mettez-les à niveau vers la génération 2 avant de les épingler. C'est mieux que d'épingler l'objet directement à la génération 0.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn