Le processus C # à forte intensité de RAM ralentit après plusieurs heures

c# html-agility-pack memory-management multithreading performance

Question

J'exécute un processus (service) C # sur un serveur chargé d'analyser les pages HTML en continu. Il s'appuie sur HTMLAgilityPack. Le symptôme est que cela devient de plus en plus lent au fil du temps.

Lorsque je lance le processus, il gère n pages / s. Après quelques heures, la vitesse descend à environ n / 2 pages / s. Il peut descendre à n / 10 après quelques jours. Le phénomène a été observé à plusieurs reprises et est plutôt déterministe. Chaque fois que le processus est redémarré, les choses reviennent à la normale.

Ce qui est très important: je peux exécuter d'autres calculs dans le même processus et ils ne sont pas ralentis: je peux atteindre 100% de la CPU avec tout ce que je veux à tout moment Le processus lui-même n'est pas lent. Seule l'analyse HTML ralentit.

Je pouvais le reproduire avec un code minimal (en réalité, le comportement dans le service d'origine est un peu plus extrême, mais ce code reproduit le comportement):

public static void Main(string[] args) {
    string url = "https://en.wikipedia.org/wiki/History_of_Texas_A%26M_University";
    string html = new HtmlWeb().Load(url).DocumentNode.OuterHtml;
    while (true) {
        //Processing
        Stopwatch sw = new Stopwatch();
        sw.Start();
        Parallel.For(0, 10000, i => new HtmlDocument().LoadHtml(html));
        sw.Stop();
        //Logging
        using(var writer = File.AppendText("c:\\parsing.log")) {
            string text = DateTime.Now.ToString() + ";" + (int) sw.Elapsed.TotalSeconds;
            writer.WriteLine(text);
            Console.WriteLine(text);
        }
    }
}

Avec ce code minimal, cela affiche la vitesse (pages par seconde) en fonction du nombre d’heures écoulées depuis le début du processus:

entrez la description de l'image ici

Toutes les causes évidentes ont été écartées:

  • les pages HTML sont plus grandes ou différentes (dans le code minimal, c'est la même page)
  • RAM totale: le processus utilise environ 500 Mo sur 32 Go
  • d'autres processus utilisent le processeur ou la RAM

Cela pourrait être quelque chose à propos de RAM et d'allocation de mémoire. Je sais que HTMLAgilityPack effectue beaucoup d'allocation de mémoire pour les petits objets (nœuds et chaînes HTML). Il est clair que l'allocation de mémoire et le multi-threading ne fonctionnent pas bien ensemble. Mais je ne comprends pas comment le processus peut devenir de plus en plus lent.

Connaissez-vous quelque chose à propos du CLR ou de Windows qui pourrait causer un traitement de plus en plus intensif en RAM (beaucoup d'allocations) de plus en plus lent? Comme par exemple pénaliser les threads faisant des allocations de mémoire d'une certaine manière?

Réponse acceptée

J'ai remarqué un comportement similaire en utilisant le HTMLAgilityPack.

J'ai constaté que lorsque les données d'un rendement commençaient à espacer les variables locales des fuites locales, les classes générées par le compilateur commençaient à poser problème. Comme aucun code n'est disponible ... voici ma trousse de secours

  1. Assurez-vous de définir la bonne stratégie . Changer la stratégie de collecte du GC dans app.config facilitera la fragmentation.
  2. Assurez-vous d’annuler les éléments lorsque vous n’en avez pas besoin, dès que vous n’en avez pas besoin, n’attendez pas que l’étendue nettoie votre mémoire car les IEnumerables sont appelés dans la méthode d’appel et dans la portée des vaiables de méthode et peuvent vivre beaucoup plus longtemps que tu penses! Ouvrez votre code dans ILSpy et examinez les classes <> d__0 (0) générées. Vous verrez des choses générées comme d __. X = X; dans ce cas, X pourrait contenir un fragment ou une page entière.
  3. Vos variables locales sont placées dans le tas, car elles ne sont pas accessibles dans les itérations IEnumable si elles ne sont pas présentes.
  4. Le verrouillage commence à poser problème, des éléments volumineux sont en train de perdre du poids dans votre 4e génération de RAM et vont commencer à bloquer le CPG. Le GC met en pause vos threads pour pouvoir effectuer un nettoyage de la mémoire.
  5. La pire chose à propos de HTMLAgility est que cela fragmente le problème

    Je suis tout à fait sûr que lorsque vous commencerez à considérer la portée de vos fragments HTML, vous constaterez que les choses vont bien commencer. Examinez votre exécution à l'aide de WinDbg dans SOS et effectuez un vidage de votre mémoire.

Comment faire ça.

  1. ouvrez WinDebug, appuyez sur F6 et joignez le processus (entrez l’ID du processus dans le champ et appuyez sur ok)
  2. puis chargez l'exécution dans votre mémoire en entrant

    .loadby sos clr

  3. puis entrez

    ! dumpheap -stat

Vous obtiendrez ensuite les éléments de mémoire alloués dans votre application avec l'adresse de mémoire et la taille, regroupés par type et triés d'un en-tête bas à un en-tête élevé. Vous verrez quelque chose comme System.String [] avec un numéro masive devant celui-ci. le suffixe que vous aimeriez étudier en premier.

Maintenant pour voir qui a que vous pouvez taper

!dumpheap -mt <heap address>

Et vous verrez les adresses qui utilisent cette table de mémoire (MT) et la taille de la RAM qu’il utilise.

maintenant cela devient intéressant, plutôt que de vous passer par x100 lignes de yode que vous tapez

!gcroot <address>

ce qu’il va imprimer, c’est le fichier et la ligne de code qui a alloué la mémoire, la classe générée par le compilateur et la variable qui vous pose problème, ainsi que les octets qu’elle contient.

C'est ce que l'on pourrait appeler le "débogage de production" et fonctionne si vous avez accès au serveur, ce que je suppose que vous avez.

J'espère avoir été utile,

Walter




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi