Question

Nous avons parfois rencontré des problèmes dans lesquels nos processus serveur de longue durée (exécutés sur Windows Server 2003) ont levé une exception en raison d'un échec d'allocation de mémoire.Nous soupçonnons que ces allocations échouent en raison de la fragmentation de la mémoire.

Par conséquent, nous avons examiné d'autres mécanismes d'allocation de mémoire qui pourraient nous aider et j'espère que quelqu'un pourra me dire le meilleur :

1) Utilisez Windows Tas à faible fragmentation

2) jemalloc - tel qu'utilisé dans Firefox 3

3) Chez Doug Lea malloc

Notre processus serveur est développé à l'aide de code C++ multiplateforme, donc toute solution serait idéalement également multiplateforme (les systèmes d'exploitation *nix souffrent-ils de ce type de fragmentation de la mémoire ?).

De plus, ai-je raison de penser que LFH est désormais le mécanisme d'allocation de mémoire par défaut pour Windows Server 2008/Vista ?...Mes problèmes actuels « disparaîtront-ils » si nos clients mettent simplement à niveau le système d'exploitation de leur serveur ?

Était-ce utile?

La solution

Tout d’abord, je suis d’accord avec les autres affiches qui suggèrent une fuite de ressources.Vous voulez vraiment exclure cela en premier.

Espérons que le gestionnaire de tas que vous utilisez actuellement dispose d'un moyen de vider l'espace libre total réel disponible dans le tas (sur tous les segments). gratuit blocs) ainsi que le nombre total de blocs sur lesquels il est divisé.Si la taille moyenne des blocs libres est relativement petite par rapport à l’espace libre total dans le tas, vous avez alors un problème de fragmentation.Alternativement, si vous pouvez vider la taille du plus grand bloc libre et la comparer à l'espace libre total, cela accomplira la même chose.Le plus grand bloc gratuit serait petit par rapport au total gratuit espace disponible sur tous les blocs si vous rencontrez une fragmentation.

Pour être très clair sur ce qui précède, dans tous les cas nous parlons de gratuit blocs dans le tas, pas les blocs alloués dans le tas.Dans tous les cas, si les conditions ci-dessus ne sont pas remplies, alors vous faire avoir une situation de fuite quelconque.

Ainsi, une fois que vous avez exclu une fuite, vous pouvez envisager d’utiliser un meilleur répartiteur. Le malloc de Doug Lea suggéré dans la question est un très bon allocateur pour les applications à usage général et très robuste la plupart du temps.En d’autres termes, il a fait ses preuves pour fonctionner très bien pour la plupart des applications.Cependant, aucun algorithme n’est idéal pour tous les applications et toute approche d'algorithme de gestion peuvent être brisées par des conditions pathologiques appropriées par rapport à leur conception.

Pourquoi rencontrez-vous un problème de fragmentation ? - Les sources des problèmes de fragmentation sont causé par le comportement d'une application et sont liés à des durées de vie d'allocation très différentes dans le même domaine de mémoire.Autrement dit, certains objets sont alloués et libérés régulièrement tandis que d'autres types d'objets persistent pendant de longues périodes, tous dans le même tas. Pensez aux objets à durée de vie plus longue qui font des trous dans des zones plus vastes de l'arène et empêchent ainsi le fusionner des blocs adjacents qui ont été libérés.

Pour résoudre ce type de problème, la meilleure chose à faire est de diviser logiquement le tas en sous-arènes où les durées de vie sont plus similaires.En effet, vous voulez un tas transitoire et un ou plusieurs tas persistants qui regroupent des éléments de durées de vie similaires.

D'autres ont suggéré une autre approche pour résoudre le problème, qui consiste à tenter de rendre les tailles d'allocation plus similaires ou identiques, mais cette solution est moins idéale car elle crée un type différent de fragmentation appelé fragmentation interne - qui est en fait l'espace perdu dont vous disposez. en allouant plus de mémoire dans le bloc que ce dont vous avez besoin.

De plus, avec un bon allocateur de tas, comme celui de Doug Lea, il n'est pas nécessaire de rendre les tailles de blocs plus similaires car l'allocateur effectuera déjà un schéma de regroupement de deux tailles qui rendra totalement inutile l'ajustement artificiel des tailles d'allocation transmises à malloc ( ) - en effet, son gestionnaire de tas le fait automatiquement pour vous de manière beaucoup plus robuste que l'application ne pourra effectuer d'ajustements.

Autres conseils

Je pense que vous avez exclu par erreur une fuite de mémoire trop tôt.Même une petite fuite de mémoire peut provoquer une grave fragmentation de la mémoire.

En supposant que votre application se comporte comme suit :
Allouer 10 Mo
Allouer 1 octet
10 Mo gratuits
(oups, nous n'avons pas libéré 1 octet, mais peu importe 1 petit octet)

Cela ressemble à une très petite fuite, vous le remarquerez à peine en surveillant uniquement la taille totale de la mémoire allouée.Mais cette fuite finira par faire ressembler la mémoire de votre application à ceci :
.
.
Gratuit – 10 Mo
.
.
[Alloué -1 octet]
.
.
Gratuit – 10 Mo
.
.
[Alloué -1 octet]
.
.
Gratuit – 10 Mo
.
.

Cette fuite ne sera pas remarquée...jusqu'à ce que vous souhaitiez allouer 11 Mo
En supposant que vos minidumps contiennent des informations sur la mémoire complète, je vous recommande d'utiliser DébogageDiag pour repérer d'éventuelles fuites.Dans le rapport de mémoire généré, examinez attentivement le nombre d'allocations (pas la taille).

Comme vous le suggérez, le malloc de Doug Lea pourrait bien fonctionner.Il est multiplateforme et a été utilisé dans le code d'expédition.À tout le moins, il devrait être facile à intégrer dans votre code à des fins de test.

Ayant travaillé dans des environnements à mémoire fixe pendant plusieurs années, cette situation constitue certainement un problème, même dans des environnements non fixes.Nous avons constaté que les répartiteurs CRT ont tendance à être assez mauvais en termes de performances (vitesse, efficacité de l'espace gaspillé, etc.).Je crois fermement que si vous avez un besoin important d'un bon allocateur de mémoire sur une longue période, vous devriez écrire le vôtre (ou voir si quelque chose comme dlmalloc fonctionnera).L'astuce consiste à écrire quelque chose qui fonctionne avec vos modèles d'allocation, et cela a plus à voir avec l'efficacité de la gestion de la mémoire qu'avec presque toute autre chose.

Essayez dlmalloc.Je donne définitivement un coup de pouce.Il est également assez réglable, vous pourrez donc peut-être obtenir plus d'efficacité en modifiant certaines options de compilation.

Honnêtement, vous ne devriez pas compter sur le fait que les choses « disparaissent » avec les nouvelles implémentations du système d’exploitation.Un service pack, un correctif ou un autre nouveau système d'exploitation N années plus tard pourrait aggraver le problème.Encore une fois, pour les applications qui nécessitent un gestionnaire de mémoire robuste, n'utilisez pas les versions standard disponibles avec votre compilateur.Trouvez-en un qui fonctionne pour ton situation.Commencez par dlmalloc et ajustez-le pour voir si vous pouvez obtenir le comportement qui convient le mieux à votre situation.

Vous pouvez contribuer à réduire la fragmentation en réduisant le montant que vous allouez et désallouez.

par exemple.Par exemple, pour un serveur Web exécutant un script côté serveur, il peut créer une chaîne vers laquelle afficher la page.Au lieu d'allouer et de désallouer ces chaînes pour chaque demande de page, conservez simplement un pool d'entre elles, de sorte que vous n'en allouez que lorsque vous en avez besoin de plus, mais vous ne désallouez pas (ce qui signifie qu'après un certain temps, vous obtenez la situation où vous n'attribuez plus non plus, parce que vous avez assez)

Vous pouvez utiliser _CrtDumpMemoryLeaks();pour vider les fuites de mémoire dans la fenêtre de débogage lors de l'exécution d'une version de débogage, mais je pense que cela est spécifique au compilateur Visual C.(c'est dans crtdbg.h)

Je soupçonnerais une fuite avant de soupçonner une fragmentation.

Pour les structures de données gourmandes en mémoire, vous pouvez passer à un mécanisme de pool de stockage réutilisable.Vous pourrez peut-être également allouer plus de choses sur la pile que sur le tas, mais en termes pratiques, cela ne fera pas une énorme différence, je pense.

Je lancerais un outil comme valgrind ou ferais une journalisation intensive pour rechercher les ressources qui ne seraient pas publiées.

@nsaners - Je suis presque sûr que le problème est dû à la fragmentation de la mémoire.Nous avons analysé minidumps cela indique un problème lorsqu'une grande partie de la mémoire (5 à 10 Mo) est allouée.Nous avons également surveillé le processus (sur site et en développement) pour vérifier les fuites de mémoire – aucune n'a été détectée (l'empreinte mémoire est généralement assez faible).

Le problème se produit sous Unix, même s'il n'est généralement pas aussi grave.

Le tas à faible fragmentation nous a aidé, mais mes collègues ne jurent que par Tas intelligent(il est utilisé sur plusieurs plates-formes dans quelques-uns de nos produits depuis des années).Malheureusement, en raison d'autres circonstances, nous n'avons pas pu utiliser Smart Heap cette fois-ci.

Nous examinons également les blocs / secouer l'allocation et essayer d'avoir des piscines / stratégies averties, c'est-à-dire des choses à long terme ici, une demande entière là-bas, des choses à court terme là-bas, etc.

Comme d'habitude, vous pouvez généralement gaspiller de la mémoire pour gagner un peu de vitesse.

Cette technique n'est pas utile pour un répartiteur à usage général, mais elle a sa place.

Fondamentalement, l'idée est d'écrire un allocateur qui renvoie la mémoire d'un pool où toutes les allocations ont la même taille.Ce pool ne peut jamais être fragmenté car n’importe quel bloc vaut un autre.Vous pouvez réduire le gaspillage de mémoire en créant plusieurs pools avec des fragments de tailles différentes et en choisissant le plus petit pool de taille de fragments toujours supérieur à la quantité demandée.J'ai utilisé cette idée pour créer des allocateurs qui s'exécutent en O(1).

La solution simple, rapide et sale consiste à diviser l'application en plusieurs processus, vous devriez obtenir un nouveau HEAP à chaque fois que vous créez le processus.

Votre mémoire et votre vitesse peuvent en souffrir un peu (échange), mais un matériel rapide et une grande RAM devraient pouvoir vous aider.

C'était une vieille astuce UNIX avec les démons, à l'époque où les threads n'existaient pas encore.

si vous parlez de Win32, vous pouvez essayer de compresser quelque chose en utilisant LARGEADDRESSAWARE.Vous disposerez d'environ 1 Go de mémoire défragmentée supplémentaire afin que votre application la fragmente plus longtemps.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top