9
votes

Interruptions de GC et TPL

J'ai un service WCF. Au cours des travaux du service, il doit appeler deux services Web. Il y a donc du code similaire à celui-ci: xxx

la plupart du temps, cela fonctionne correctement, mais je voyais de temps en temps des pics dans l'heure d'exécution, où la première tâche a pris quelques secondes pour commencer même. En regardant Perfmon, j'ai réalisé que c'était exactement lorsque GC se passait. Apparemment, GC était une priorité plus élevée puis exécutant mes tâches. Ce n'est pas acceptable, car la latence est très importante pour moi et que je préférerais que GC se produise entre les demandes et non au milieu d'une demande.

J'ai tenté d'aller de manière différente, et Au lieu de tourner mes propres tâches, j'ai utilisé WebClient.DownloadstringTask . xxx

Cela n'a pas aidé; Le GC fonctionne maintenant après le début de la tâche, mais avant la continuation. Encore une fois, je suppose que cela a figuré que le système est maintenant inactif, il est donc bon de commencer GC. Seulement, je ne peux pas me permettre la latence.

Utilisation de tâchesCreatiàOptionOptions.LONGRunning, qui provoque le planificateur d'utiliser des fils non thread-piscine, semble résoudre ce problème, mais je ne veux pas créer tant de nouvelles Threads - Ce code va courir beaucoup (plusieurs fois par demande).

Quel est le meilleur moyen de surmonter ce problème?


20 commentaires

Juste combien de temps est l'interruption de GC?


N'importe où entre 0,5 et 3 secondes.


Une solution hacky serait d'appeler gc.collect après l'appel, mais si vous avez de nombreuses demandes, cela sera contre-intuitif. Avez-vous essayé de régler les paramètres GC, c'est-à-dire gclatencyMode.LowLatence ? Aussi, est-ce .NET 2 ou 4? Si .NET 2, vous devrez peut-être activer Concurrence GC dans le fichier de configuration. Ou la désactivation peut offrir une meilleure latence.


.NET 4.0, à l'aide du serveur GC. N'a pas essayé GclatencyMode, vérifiera


Essayez de désactiver le serveur GC. Sous Server GC, il y aura 10 fois moins de GC, mais avec une latence plus élevée.


En fait, Server GC nous a donné un avantage de performance énorme lorsque plusieurs utilisateurs faisaient des demandes. On dirait que gclatencyMode n'est pas disponible pour le serveur GC.


Êtes-vous sûr que cela est causé par le GC? Il ressemble plus à la threadpool prend un certain temps pour planifier votre tâche S, car il exécute déjà une autre tâche S sur tous les CPU. Une autre possibilité est qu'elle est causée par defaultconnectionLimit .


Les pics corrélent définitivement aux pics de GC.


En outre, d'autres cœurs CPU ne sont pas occupés.


Si c'est GC, je ne suis pas sûr de la longueur de la frondante. FWIW, Wetowall gaspille un fil. Quel est le comportement si vous appelez gc.Collect à la fin de la demande avec le code d'origine?


Appel GC.Collect à la fin ne l'aide, car il y a une grande quantité de mémoire consommée lors de la demande, et la GC (je suppose) est PASSENT pour libérer cette mémoire. Je suis également pas sûr pourquoi LongRunning corrige ce problème. On dirait que ces discussions commencent leur travail immédiatement, et ne sont pas en attente d'être appelé à travailler, si GC ne soit pas la chance d'interruption.


GC n'est pas déclenché par le ralenti. La cause la plus courante est la répartition, alors je suppose que vos tâches allouent beaucoup.


Ils n'acceptent rien réellement, autre qu'un objet webclient


Et aussi, le délai est même avant que les tâches commencent


@DoronyAacoby Délai avant de démarrer la tâche est une autre chose qui pointe vers threadpool .


Comme dit auparavant, cela ne ressemble pas à un problème de GC. Comment vous êtes-vous assuré que c'est le GC, exactement? Quelle est la taille de votre tas? Un GC ne devrait pas prendre des secondes pour la plupart des tas.


J'ai couru des parfums avec le compteur "% temps dans gc". J'ai ensuite corrélé les temps des ralentissements (où ma tâche ne démarrerait pas) avec des moments où ce compteur était élevé. Ils étaient exactement corrélés. Chaque fois que la «tâche ne démarre pas» est arrivée, il y avait une pointe dans le comptoir.


Si le problème vraiment est le GC (qui, comme d'autres l'ont souligné, semble improbable), la réponse est de générer moins de déchets


Je suis en train de comprendre cela aussi. Mais je veux vraiment que le GC n'interfère pas à ce stade. Que cela se comporterait exactement comme si cela se serait comporté s'il s'agissait d'une opération synchrone.


Et je ne comprends toujours pas pourquoi tout le monde insiste sur ce numéro n'est pas une question de GC lorsque les données ne l'indifférent distinctement.


5 Réponses :


1
votes

Je sais que votre question concerne GC, mais j'aimerais commencer par parler d'une implémentation ASYNC d'abord, puis voir si vous allez toujours souffrir des mêmes problèmes.

sortir de votre mise en œuvre initiale Exemple de code, vous allez gaspiller trois threads CPU en attente d'E / S en ce moment:

  • Le premier fil étant gaspillé est le fil d'E / S original WCF qui exécute l'appel. Ça va être bloqué par la tâche.Waitall tandis que les tâches de l'enfant sont encore exceptionnelles.
  • Les deux autres threads en cours de perte sont les threads de piscine de thread que vous utilisez pour exécuter les appels vers Service1 et Service2

    Tout ce temps tandis que les E / S à Service1 et Service2 sont exceptionnels Les trois threads de la CPU que vous gaspillez ne sont pas capables d'être utilisés pour exécuter d'autres travaux et le GC doit incliner des boutons. < / p>

    Par conséquent, ma recommandation initiale serait de modifier la méthode de votre WCF lui-même pour utiliser le modèle de modèle de programmation asynchrone (APM) supporté par le temps d'exécution de la WCF. Cela résout le problème du premier fil gaspillé en permettant au fil d'E / S de WCF d'origine qui appelle immédiatement à votre mise en œuvre de votre service à sa piscine pour pouvoir servir d'autres demandes entrantes. Une fois que vous avez fait cela, vous voulez ensuite apporter les appels à Service1 et Service2 Asynchrone également du client Perspectice. Cela impliquerait une des deux choses:

    1. générer des versions asynchrones de leurs interfaces contractuelles qui utilisent à nouveau l'APM BEGNXXX / endxxx que WCF prend également en charge le modèle client.
    2. Si ce sont des services de repos simples que vous parlez, vous avez les autres choix ASYNC suivants:
      • webclient :: downloadstringasync implémentation ( WebClient n'est pas mon API FAV personnellement)
      • httpwebrequest :: Begingetresponse + HTTPWEBRESPONSEXESTREAM + HTTPWEBREQUEST :: DébutRead
      • GO Saignement Edge avec le nouveau site Web de l'API'S> httpClient

        Mettre tout cela ensemble, il n'y aurait pas de fils gaspillés pendant que vous attendez une réponse de Service1 et Service2 dans votre service. Le code ressemblerait à ce que cela suppose que vous preniez un itinéraire client WCF: xxx

        une fois que vous avez tout cela en place, vous n'avez techniquement aucun fil de processeur exécutant pendant que le Les E / S avec Service1 et Service2 sont exceptionnels. Ce faisant, il n'ya pas de threads pour que le GC ne soit même à craindre d'interrompre la plupart du temps. La seule fois où il y aura des travaux de processeur réels qui se produira désormais est la planification originale du travail, puis la poursuite de la poursuite sur la continualité où vous gérez des exceptions et massez les résultats.


3 commentaires

Merci pour la réponse approfondie. Mon contrat de service me force à être synchrone (mes clients ne sont pas des clients .NET), je dois donc attendre que les deux appels de service soient revenus afin de continuer à traiter la demande. Vous avez raison que mon premier échantillon de code déteste trois threads, mais dans le deuxième échantillon, je fais quelque chose de très similaire à ce que vous avez suggéré (sauf que mon service est synchrone, je dois donc attendre que les tâches de DownloadsStringTask soient complètes), mais GC interrompt toujours.


@Doronyaacoby juste parce que le client doit être synchrone (.NET ou autre) ne signifie pas que votre mise en œuvre de service doit être. C'est une décision de côté du serveur d'être asynchrone ou non. Changer votre contrat de service Pour être Async ne changera pas le niveau de savon de la méthode du tout. J'espère que vous examinerez à nouveau car cela ne profitera que de vous. En fin de compte, le GC est juste un autre fil qui doit concurrencer vos threads d'application et moins votre demande est consacrée au blocage des appels externes, plus le GC sera facilement capable de faire son travail. Bonne chance!


Oh, je n'ai pas réalisé ça. Quoi qu'il en soit, le changement serait toujours énorme et me demanderait de changer de centaines d'interfaces (il s'agit d'un appel de bas niveau à l'intérieur d'un système assez complexe). Pas très plausible pour ce service spécifique.



0
votes

Je vous recommande de reconsidérer la réponse de Drew. Un système entièrement asynchrone serait idéal.

Mais si vous souhaitez modifier moins de code, vous pouvez utiliser fromasync code> au lieu de startnew code> (ceci nécessite des proxies asynchrones pour Service1 code> et Service2 code>): p>

var task1 = Task.Factory.FromAsync(_service1.BeginRun, _service1.EndRun, query, null);
var task2 = Task.Factory.FromAsync(_service2.BeginRun, _service2.EndRun, query, null);
Task.WaitAll(task1, task2);


2 commentaires

Le code ASYNC n'évitera pas la latence GC et il produira encore plus de déchets.


Il y aura toujours une latence GC, mais je ne suis pas sûr que cela produira plus de déchets. Je ne l'ai pas mesuré, mais je suppose que c'est à peu près la même chose. Mais cette solution soulage beaucoup de pression sur la piscine de thread.



3
votes

Permettez-moi de nettoyer d'abord quelques malentendus observés sur cette page:

  • GC n'a pas lieu quand il est inactif. Il a lieu lorsqu'il est déclenché en raison d'une allocation de défaite (nouvelle), de la pression de mémoire GC.Collect ou du système d'exploitation
  • Le GC peut arrêter les threads d'application. Il ne fonctionne pas simultanément (au moins pendant un certain temps)
  • "% heure dans gc" est un compteur qui ne change pas entre GCS qui signifie que vous pouvez voir une valeur fade
  • Le code ASYNC n'aide pas avec les problèmes de GC. En fait, il génère plus ordures (tâches, iaysyncresultsults et probablement autre chose)
  • L'exécution de votre code sur des threads dédiés ne les empêche pas d'être arrêté

    Comment réparer cela?

    1. génère moins de déchets. Joindre un profileur de mémoire (Jetbrains est facile à utiliser) et voyez ce qui générait des ordures et ce qui se trouve sur votre tas
    2. Réduisez la taille du tas pour réduire la durée de pause (un tas de 3 Go est probablement dû à une certaine mise en cache? Peut-être rétrécir le cache?)
    3. Démarrez plusieurs sites ASP.NET avec la même application, reconnectez les notifications GC pour détecter un GC à venir et prendre certains des sites IIS hors de la rotation d'équilibrage de la charge lorsqu'ils ont une gc ( http: //blogs.msdn .COM / B / JCLAUZEL / Archive / 2009/12/10 / GC-Notifications-ASP-Net-Server-Workloads.aspx? redirigé = vrai )

      Vous remarquerez qu'il n'y a pas de solution facile. Je ne connais pas un, mais si le problème est causé par GC, celui de ce qui précède résoudra le problème.


9 commentaires

Merci. Si ce que vous dites est vrai, comment expliquez-vous le fait que GC n'interrompt pas si je fais des appels de service synchrones au lieu d'appels asynchronisés?


Combien d'appels de service simultanés avez-vous? Beaucoup (comme 50)? Dans votre question, vous avez dit que le problème ne se produit pas lors de l'utilisation de la tâche longueRunning, peu importe si synchrone ou asynchrone. Correct?


Non, juste deux appels. Et correct, la frénning la résout (bien que non évolutive, bien sûr).


À propos de vos autres questions: Oui, nous mettons en cache beaucoup de choses dans le démarrage du processus. Réduire ce n'est pas vraiment une option, nous avons besoin que les choses soient rapides que possible et ne peuvent pas charger du disque au milieu d'une demande. De plus, je devrais mentionner que nous ne courons pas sur IIS, c'est l'auto-hébergement de la WCF.


Combien de fois cette pause se produit-elle (en pourcentage des appels que vous faites)? Comme vous ne pouvez pas rétrécir votre tas, j'essaierais une suggestion (1) et votre profil où provient les ordures.


Cela arrive dans environ 2% des appels. J'essaie de profiler maintenant, mais l'homme, le profilage avec un si grand tas est lent comme l'enfer


(Et cela n'expliquerait toujours pas la différence entre les opérations de threadpool / longrunning / synchrones)


Le code ASYNC génère plus de déchets que de code synchroneux, mais Way des ordures moins que des fils explicites.


Les fils de piscine de fil sont réutilisés et ne génèrent pas des ordures. En outre, où pensez-vous que les continuations asynchreures fonctionnent-elles? Sur le fil-piscine.



0
votes

Vous voudrez peut-être essayer cela, mais cela pourrait simplement lancer le problème sur la route un peu:

try
{
    GCSettings.LatencyMode = GCLatencyMode.LowLatency;

    // Generation 2 garbage collection is now
    // deferred, except in extremely low-memory situations

    var task1 = Task.Factory.StartNew(() => _service1.Run(query));
    var task2 = Task.Factory.StartNew(() => _service2.Run(query));
    Task.WaitAll(new[] { task1 , task2 });
}
finally
{
    // ALWAYS set the latency mode back
    GCSettings.LatencyMode = oldMode;
}


2 commentaires

La faible disponibilité ne fonctionne pas pour le serveur GC, ce que j'utilise


Intéressant - est-ce par conception ou juste votre observation? Merci pour les commentaires, bon de savoir.



0
votes

Lorsque vous exécutez de nombreuses demandes Web, vous chargez de nombreux objets temporaires dans le tas géré. Bien que le tas cultive le GC essaiera de libérer de la mémoire avant d'attribuer un nouveau segment de GC. C'est la principale raison pour laquelle vous voyez GCS se produisant pendant que vous travaillez.

vient maintenant la partie intéressante: votre tasse de tasse est déjà de 3 Go et que vous avez des demandes Web avec objets courts en outre sur le tas de GC. Plein GCS prendra beaucoup de temps pour traverser votre graphique d'objet certainement complexe (tous les 3 Go) pour des objets morts. Dans un tel scénario de débit élevé où vous obtenez pour chaque demande, une quantité substantielle de données temporaires sur le fil vous fera forcer beaucoup de GCS.

À ce stade, vous êtes limité GC: la performance de l'application n'est plus sous votre contrôle. Vous pouvez résoudre ce problème normalement en conception minutieuse de vos structures de données et de vos modèles d'accès, mais le GC Times sera en grande partie (je suppose que 95%) domine votre performance de l'application.

Il n'y a pas de moyen facile de cela. Pour que les SEGMETN de GC sont plus petits en vérifiant que votre consommation de mémoire globale peut être difficile s'il s'agit d'un système complexe important. Une alternative pourrait être de se produire d'un processus supplémentaire (non pas une nouvelle Appdomain puisque le GC n'est pas au courant des appdomains du tout) et créez vos objets de courte durée dans vos demandes Web. Ensuite, vous POUVEZ SORTIR DE CE DROP SI VOUS POUVEZ Calculer une réponse significative dans votre petit processus, qui est ensuite utilisé par votre processus de grand serveur. Si votre processus crée une quantité égale de données TEMP comme vos demandes Web d'origine, vous êtes de retour sur Square One et que vous n'avez rien gagné.

Cela pourrait aider à réutiliser des objets des demandes Web précédentes et de conserver un pool d'objets prêts à réduire le nombre d'allocations.

Si vous avez beaucoup de chaînes identiques dans votre tas de processus, cela pourrait vous aider à les interneriser si elles ne sont jamais libérées de toute façon. Cela pourrait aider à simplifier votre graphique d'objet.


2 commentaires

Suis-je pas allouer la même quantité de mémoire exacte lorsque vous utilisez des tâches longues? Ou lorsque vous faites des demandes Web synchrones? Pourquoi ça ne va-t-il pas là-bas? En outre, le GC qui arrive est Gen1 et non complet GC.


Pour savoir pourquoi vous avez besoin d'obtenir des piles d'appels lorsqu'un GC se produit. Vous pouvez vous casser dans WINDBG pour les exceptions de notification CLR qui devrait vous donner une opération de clue Whis déclenchent une GC.