48 votes

Comment prendre le contrôle d'un segment de mémoire de 5 Go dans Haskell?

Actuellement, je suis en train d'expérimenter avec un peu de Haskell serveur web écrit en composant logiciel Enfichable qui charge et met à la disposition du client un grand nombre de données. Et j'ai une très, très dur le temps de prendre le contrôle sur le processus de serveur. À des moments aléatoires les processus utilise beaucoup de CPU pour les secondes, les minutes et devient irresponsive aux demandes des clients. Parfois, l'utilisation de la mémoire pointes (et parfois gouttes) des centaines de méga-octets en quelques secondes.

J'espère que quelqu'un a plus d'expérience avec des longue Haskell processus qui utilisent beaucoup de mémoire et peut me donner quelques conseils pour rendre la chose plus stable. J'ai été le débogage de la chose pendant des jours maintenant et je commence à avoir un peu désespérée ici.

Un petit aperçu de mon installation:

  • Sur le démarrage du serveur, j'ai lu sur 5 giga-octets de données dans un grand (imbriqué) de Données.La carte-comme la structure en mémoire. Le imbriquée de la carte est de valeur stricte et toutes les valeurs à l'intérieur de la carte sont des types de données avec tous leur domaine complètement ainsi. J'ai mis beaucoup de temps à s'assurer de pas non évaluée thunks sont à gauche. L'importation (selon mon système de charge) prend environ 5 à 30 minutes. La chose étrange est la fluctuation des séries consécutives est plus grand que je m'attends, mais c'est un autre problème.

  • Le big data structure de vie à l'intérieur d'un 'TVar" qui est partagé par tous les threads de client générée par le composant logiciel Enfichable serveur. Les Clients peuvent demander des parties arbitraires de données à l'aide d'un petit langage de requête. Le montant de la demande de données est généralement de petite taille (jusqu'à 300 ko environ) et ne touche qu'une petite partie de la structure de données. Tous en lecture seule demande sont effectués à l'aide d'un readTVarIO', de sorte qu'ils ne nécessitent aucune STM transactions.

  • Le serveur est démarré avec les indicateurs suivants: +RTS-N -I0 -qg-qb. Cela démarre le serveur en mode multithread, désactiver le temps d'inactivité et parallèle GC. Cela semble accélérer le processus beaucoup.

Le serveur fonctionne principalement sans aucun problème. Cependant, chaque maintenant et puis une demande d'un client et les pics de l'UC à 100% (ou même plus de 100%) et continue de le faire pour un long moment. Pendant ce temps, le serveur ne répondent pas à la demande plus.

Il y a peu de raisons que je pense que peut entraîner l'utilisation du PROCESSEUR:

  • La demande prend juste beaucoup de temps car il y a beaucoup de travail à faire. C'est peu probable parce que parfois, il arrive pour les demandes qui ont prouvé être très rapide dans les courses précédentes (avec rapide je veux dire 20-80ms ou presque).

  • Il y a encore quelques non évaluée thunks qui doivent être calculées avant que les données peuvent être traitées et envoyées au client. C'est d'ailleurs peu probable, avec la même raison que pour le point précédent.

  • En quelque sorte, la collecte des ordures coups de pied dans et commencer la numérisation de l'ensemble de mon 5GO tas. Je peux imaginer que cela peut prendre beaucoup de temps.

Le problème est que je n'ai aucune idée de comment comprendre ce qui se passe exactement et quoi faire à ce sujet. Parce que le processus d'importation prend beaucoup de temps de profilage des résultats de ne pas me montrer quelque chose d'utile. Il semble y avoir aucun moyen conditionnelle, d'activer et de désactiver le générateur de profils à partir de code.

Personnellement, je soupçonne que le GC est le problème ici. Je suis en utilisant GHC7 qui semble avoir beaucoup d'options pour adapter la façon GC fonctionne.

Ce GC paramètres recommandez-vous lors de l'utilisation de grands tas généralement très stable de données?

29voto

Edward Z. Yang Points 13760

Grande utilisation de la mémoire et occasionnellement, des pics de l'UC est presque certainement le GC coups de pied dans. Vous pouvez voir si c'est effectivement le cas à l'aide de la RTS, comme les options de -B, ce qui provoque GHC à émettre un bip à chaque fois qu'il y a une importante collection, -t qui vous dira les statistiques après le fait (voir en particulier si le GC temps sont vraiment très long) ou -Dg, qui tourne sur les informations de débogage pour GC appels (si vous avez besoin de compiler avec -debug).

Il y a plusieurs choses que vous pouvez faire pour remédier à ce problème:

  • Sur l'importation initiale des données, GHC est perdre beaucoup de temps à la croissance de la tas. Vous pouvez dire à saisir la totalité de la mémoire dont vous avez besoin à la fois par la spécification d'un grand -H.

  • Un gros tas avec stable de données va être promu dans une ancienne génération. Si vous augmenter le nombre de générations avec -G, vous pouvez être en mesure d'obtenir les données stables pour être dans la plus ancienne, très rarement GC avais génération, alors que vous avez le plus traditionnel des jeunes et des vieux tas au-dessus d'elle.

  • En fonction de la sur l'utilisation de la mémoire du reste de l'application, vous pouvez utiliser -F tweak combien de GHC permettra à l'ancienne génération de grandir avant de recueillir à nouveau. Vous pouvez être capable de modifier ce paramètre pour faire ça un de ces ordures.

  • Si il n'y a pas écrit, et vous avez une interface bien définie, il peut être intéressant de faire cette mémoire géré par les nations unies par le GHC (utiliser le C FFI), de sorte qu'il n'y a aucune chance d'un super-GC jamais.

Ce sont toutes les spéculations, de sorte s'il vous plaît vérifier avec votre application particulière.

2voto

J'ai eu un problème similaire avec 1,5 GO de tas de imbriquée Cartes. Avec le ralenti GC, par défaut, je voudrais obtenir de 3-4 secondes de freeze sur tous les GC, et au ralenti GC off (+RTS-I0), je voudrais obtenir de 17 secondes de freeze après quelques centaines de requêtes, provoquant un client de délai.

Ma "solution" a été la première à augmenter le client en temps et en demandant aux individus de tolérer que tandis que 98% des requêtes ont été d'environ 500ms, environ 2% des requêtes seraient mort lente. Cependant, vouloir une meilleure solution, j'ai fini par l'exécution de deux serveurs à charge équilibrée et de leur mise hors ligne de la grappe pour performGC tous les 200 requêtes, puis de nouveau en action.

Ajoutant l'insulte à la blessure, c'était une réécriture d'un original programme en Python, qui n'a jamais eu de tels problèmes. Par souci d'équité, nous avons obtenu environ 40% d'augmentation de la performance, morts-facile parallélisation et d'une plus grande stabilité de la base de code. Mais ce satanés GC problème...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X