2010-04-02 5 views
12

J'utilise Delphi 2009 qui intègre le gestionnaire de mémoire FastMM4.Pourquoi la mémoire de mon programme Delphi continue-t-elle de croître?

Mon programme lit et traite un grand ensemble de données. Toute la mémoire est libérée correctement chaque fois que j'efface l'ensemble de données ou quitte le programme. Il n'a pas de fuites de mémoire du tout. En utilisant la routine CurrentMemoryUsage donnée dans la réponse de spenwarr à: How to get the Memory Used by a Delphi Program, j'ai affiché la mémoire utilisée par FastMM4 pendant le traitement.

Ce qui semble se produire, c'est que la mémoire est en train d'augmenter après chaque cycle de processus et de publication. par exemple:

1,456 Ko utilisé après le démarrage de mon programme sans jeu de données.

218,455 Ko utilisé après le chargement d'un grand ensemble de données. 71,994 Ko après suppression complète de l'ensemble de données. Si je sors à ce point (ou à n'importe quel point dans mon exemple), aucune fuite de mémoire n'est signalée.

271,905 Ko utilisé après le chargement du même jeu de données.

125,443 Ko après suppression complète de l'ensemble de données.

325,519 Ko utilisé après avoir rechargé le même jeu de données.

179,059 Ko après suppression complète de l'ensemble de données.

378 752 Ko utilisé après le chargement du même jeu de données.

Il semble que l'utilisation de la mémoire de mon programme augmente d'environ 53 400 Ko à chaque cycle de chargement/déchargement. Le gestionnaire des tâches confirme que cela se passe réellement. J'ai entendu dire que FastMM4 ne libère pas toujours toute la mémoire du programme sur le système d'exploitation lorsque les objets sont libérés afin de pouvoir garder de la mémoire quand il en a besoin. Mais cette croissance continuelle me dérange. Comme aucune fuite de mémoire n'est signalée, je ne peux pas identifier un problème. Est-ce que quelqu'un sait pourquoi cela se produit, s'il est mauvais, et si je peux ou dois faire quelque chose à ce sujet?


Merci dthorpe et Mason pour vos réponses. Tu m'as fait réfléchir et essayer des choses qui m'ont fait réaliser qu'il me manquait quelque chose. Un débogage aussi détaillé était nécessaire.

En fin de compte, toutes mes structures étaient correctement libérées à la sortie. Mais la libération de la mémoire après chaque cycle pendant la course n'était pas. C'était accumuler des blocs de mémoire qui auraient normalement causé une fuite qui aurait été détectable à la sortie si mon nettoyage de sortie n'était pas correct - mais c'était le cas.

Il y avait des StringLists et d'autres structures dont j'avais besoin pour effacer entre les cycles. Je ne suis toujours pas sûr de la façon dont mon programme a fonctionné correctement avec les données supplémentaires encore présentes dans les cycles précédents, mais c'est le cas. Je ferai probablement des recherches plus approfondies.

Cette question a été répondue. Merci de votre aide.

+0

à mon humble avis, vous devriez avoir ajouté votre réponse et accepté au lieu de modifier la question. Il est plus facile à quelqu'un qui s'occupe de trouver la question, la réponse et d'apprendre de votre expérience. – EMBarbosa

Répondre

25

L'utilitaire CurrentMemoryUsage que vous avez lié à indique la taille de jeu de travail de votre application.L'ensemble de travail est le nombre total de pages d'espace d'adressage de mémoire virtuelle mappées à des adresses de mémoire physique. Cependant, certaines ou plusieurs de ces pages peuvent contenir très peu de données réelles. L'ensemble de travail est donc la «limite supérieure» de la quantité de mémoire utilisée par votre processus. Il indique la quantité d'espace d'adressage réservée à l'utilisation, mais n'indique pas la quantité réellement validée (résidant réellement dans la mémoire physique) ou la quantité de pages validées qui sont réellement utilisées par votre application. Essayez ceci: après avoir vu votre taille de travail grimper après plusieurs essais, minimisez la fenêtre principale de votre application. Vous verrez très probablement la taille de l'ensemble de travail diminuer considérablement. Pourquoi? Parce que Windows effectue un appel SetProcessWorkingSetSize (-1) lorsque vous réduisez une application qui supprime les pages inutilisées et réduit le jeu de travail au minimum. Le système d'exploitation ne le fait pas lorsque la fenêtre de l'application est de taille normale car la réduction de la taille de l'ensemble de travail peut souvent aggraver les performances en forçant les données à être rechargées à partir du fichier d'échange. Pour entrer plus en détail: Votre application Delphi alloue de la mémoire en morceaux assez petits - une chaîne ici, une classe là. L'allocation de mémoire moyenne pour un programme est généralement inférieure à quelques centaines d'octets. Il est difficile de gérer efficacement de petites allocations à l'échelle du système, ce qui n'est pas le cas du système d'exploitation. Il gère efficacement les blocs de mémoire volumineux, en particulier à la taille de la page de la mémoire virtuelle 4k et à la taille minimale de la plage d'adresses de la mémoire virtuelle de 64k.

Cela présente un problème pour les applications: les applications allouent généralement de petits blocs, mais le système d'exploitation distribue la mémoire en morceaux plutôt volumineux. Que faire? Réponse: suballocate. Le gestionnaire de mémoire de la bibliothèque d'exécution Delphi et le gestionnaire de mémoire de remplacement FastMM (ainsi que les bibliothèques d'exécution de presque tous les autres langages ou outils de la planète) existent pour faire une chose: découper de grands blocs de mémoire du système d'exploitation en plus petits blocs utilisés par l'application. Garder la trace de l'endroit où se trouvent tous les petits blocs, de leur taille, et s'ils ont été «divulgués» nécessite aussi de la mémoire - appelée surcharge. Dans les situations d'allocation/désallocation de mémoire importante, il peut arriver que vous libérez 99% de ce que vous avez alloué, mais la taille de l'ensemble de travail du processus ne diminue que de 50%, par exemple. Pourquoi? Le plus souvent, cela est dû à la fragmentation du segment de mémoire: un petit bloc de mémoire est toujours utilisé dans l'un des gros blocs que le gestionnaire de mémoire Delphi a obtenus du système d'exploitation et qu'il a divisés en interne. Le compte interne de la mémoire utilisée est petit (300 octets, disons) mais puisqu'il empêche le gestionnaire de segments de relâcher le gros bloc qui est dans le système d'exploitation, la contribution de ce petit bloc de 300 octets est plus proche de 4k (ou 64k selon qu'il s'agit de pages virtuelles ou d'espace d'adressage virtuel - je ne m'en souviens pas). Dans une lourde opération de mémoire impliquant des mégaoctets de petites allocations de mémoire, la fragmentation de tas est très commune - en particulier si les allocations de mémoire pour les choses non liées à l'opération de mémoire intensive se déroulent en même temps que le gros travail. Par exemple, si le fonctionnement de votre base de données de 80 Mo génère également un état dans une zone de liste au fur et à mesure de son avancement, les chaînes utilisées pour signaler l'état seront dispersées dans le tas parmi les blocs de mémoire de base de données. Lorsque vous libérez tous les blocs de mémoire utilisés par le calcul de base de données, les chaînes de la liste sont toujours là (en cours d'utilisation, non perdues) mais elles sont dispersées partout, occupant potentiellement un gros bloc OS complet pour chaque petite chaîne. Essayez l'astuce de minimisation de la fenêtre pour voir si cela réduit votre jeu de travail. Si c'est le cas, vous pouvez réduire la "gravité" apparente des nombres renvoyés par le compteur de jeu de travail. Vous pouvez également ajouter un appel à SetProcessWorkingSetSize après votre grande opération de calcul pour purger les pages qui ne sont plus utilisées.

+0

Il ne devrait pas voir autant de mémoire perdue à la fragmentation de tas. FastMM est spécialement conçu pour maintenir la fragmentation à un minimum absolu. –

+5

Oui, je suis au courant de la conception de FastMM. J'ai aidé à critiquer sa mise en œuvre et l'intégrer au produit Delphi. FastMM est toujours sensible à des tailles de travail surestimées. – dthorpe

+0

Merci, mais le minimum a eu un effet minime et n'a pas résolu le problème. J'ai également essayé la routine de nettoyage de la mémoire de Gabr à: http://stackoverflow.com/questions/2031577/can-memory-be-cleaned-up/2033393#2033393 et ​​cela n'a pas aidé non plus. p.s. La réponse de Barry est très informative. – lkessler

1

Quel type de jeu de données utilisez-vous?Si elle est implémentée complètement dans Delphi, (ne pas appeler un autre code avec un autre gestionnaire de mémoire, comme Midas), vous pourriez essayer de fuir délibérément l'ensemble de données.

Je suppose que votre jeu de données est sur un formulaire, et il est libéré automatiquement lorsque le formulaire efface ses composants. Essayez de mettre MyDataset := nil; dans OnDestroy de votre formulaire. Cela s'assurera que l'ensemble de données fuit, et aussi tout ce que l'ensemble de données possède. Essayez cela après le chargement une fois et à nouveau après avoir chargé deux fois et comparer les rapports de fuite, et voir si cela vous donne quelque chose d'utile.

+0

@Mason: Peut-être que mon terme "dataset" est trompeur. Par ensemble de données, je veux dire un fichier d'entrée de type texte ou Unicode de grande taille que je lis en tant que FileStream, ainsi que les structures de données internes que je crée à partir de celui-ci. Je n'utilise pas de base de données. Mon programme est un seul EXE. Je n'appelle aucune DLL externe. Mon soupçon, en raison de la grande taille du morceau de mémoire perdu, qu'il a quelque chose à voir avec ce flux de fichiers et peut-être le tampon que je charge. Mais aucune fuite n'est signalée par EurekaLog ou par AQTime. Je suis dans le débogage lourd maintenant. – lkessler

0

Vous avez une mémoire à demi-fuite; évidemment. Vous perdez de la mémoire lorsque le programme est en cours d'exécution, mais lorsque vous fermez le programme, votre jeu de données est correctement libéré afin que FastMM (à juste titre) ne le signale pas.

Voir ce pour plus de détails: My program never releases the memory back. Why?

Questions connexes