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: 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. P> J'ai tenté d'aller de manière différente, et Au lieu de tourner mes propres tâches, j'ai utilisé 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. P> 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). P> Quel est le meilleur moyen de surmonter ce problème? P> P>
5 Réponses :
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: P>
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: p>
webclient :: downloadstringasync code> implémentation ( WebClient code> n'est pas mon API FAV personnellement) LI>
-
httpwebrequest :: Begingetresponse Code> + HTTPWEBRESPONSEXESTREAM CODE> + HTTPWEBREQUEST :: DébutRead Code> Li>
- GO Saignement Edge avec le nouveau site Web de l'API'S> httpClient CODE> LI>
ul> li>
OL>
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: p> xxx pré> 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. P> P>
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.
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);
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.
Permettez-moi de nettoyer d'abord quelques malentendus observés sur cette page: P>
Comment réparer cela? P>
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. P>
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 i> 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.
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; }
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.
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. p>
vient maintenant la partie intéressante: votre tasse de tasse est déjà de 3 Go et que vous avez des demandes Web avec À 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. p>
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 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. p>
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. P>
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.
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 code> 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 code>? 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 code> prend un certain temps pour planifier votre tâche
code> S, car il exécute déjà une autre tâche
code> S sur tous les CPU. Une autre possibilité est qu'elle est causée par
defaultconnectionLimit code >
.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 code> code> est une autre chose qui pointe vers
threadpool code>.
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 i> 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.