Question

Je lis dans un grand fichier texte de 1,4 million de lignes d'une taille de 24 Mo (moyenne de 17 caractères par ligne).

J'utilise Delphi 2009 et le fichier est au format ANSI, mais il est converti au format Unicode lors de la lecture. Vous pouvez donc dire que le texte converti a une taille de 48 Mo.

(Edit: j'ai trouvé un exemple beaucoup plus simple ...)

Je charge ce texte dans une simple liste de chaînes:

  AllLines := TStringList.Create;
  AllLines.LoadFromFile(Filename);

J'ai constaté que les lignes de données semblent prendre beaucoup plus de mémoire que leurs 48 Mo.

En fait, ils utilisent 155 Mo de mémoire.

Cela ne me dérange pas que Delphi utilise 48 Mo, voire 60 Mo, ce qui permet une surcharge de gestion de la mémoire. Mais 155 Mo semble excessif.

Ce n'est pas une faute de StringList. J'ai précédemment essayé de charger les lignes dans une structure d'enregistrement et j'ai obtenu le même résultat (160 Mo).

Je ne vois ni ne comprends ce qui pourrait amener Delphi ou le gestionnaire de mémoire FastMM à utiliser trois fois plus de mémoire que nécessaire pour stocker les chaînes. L'allocation de tas ne peut pas être aussi inefficace, n'est-ce pas?

Je l'ai débogué et recherché autant que je peux. Toute idée sur les raisons pour lesquelles cela pourrait se produire, ou des idées qui pourraient m'aider à réduire l'utilisation excessive, seraient grandement appréciées.

Remarque: j’utilise cette option "plus petite". fichier à titre d'exemple. J'essaie vraiment de charger un fichier de 320 Mo, mais Delphi demande plus de 2 Go de RAM et manque de mémoire en raison de cette exigence de chaîne excédentaire.

Addenum: Marco Cantu vient de publier un livre blanc sur Delphi et Unicode . . Delphi 2009 a augmenté la surcharge par chaîne de 8 octets à 12 octets (plus peut-être 4 autres pour le pointeur réel sur la chaîne). Un supplément de 16 octets par 17 x 2 = ligne de 34 octets ajoute près de 50%. Mais je vois plus de 200% de frais généraux. Quels pourraient être les 150% supplémentaires?

Succès !! Merci à tous pour vos suggestions. Vous m'avez tous fait réfléchir. Mais je dois donner crédit à Jan Goyvaerts pour la réponse, car il a demandé:

  

... pourquoi utilisez-vous TStringList? Le fichier doit-il réellement être stocké en mémoire sous forme de lignes séparées?

Cela m'a conduit à la solution: au lieu de charger le fichier de 24 Mo sous forme de StringList de 1,4 million de lignes, je peux regrouper mes lignes dans des groupes naturels que mon programme connaît. Ainsi, 127 000 lignes ont été chargées dans la liste de chaînes.

Chaque ligne contient en moyenne 190 caractères au lieu de 17 caractères. Le temps système par ligne est identique, mais il y a maintenant beaucoup moins de lignes.

Lorsque j'applique cela à un fichier de 320 Mo, il ne manque plus de mémoire, il est maintenant chargé dans moins de 1 Go de RAM. (Et cela ne prend que 10 secondes environ, ce qui est plutôt bien!)

Un traitement supplémentaire sera nécessaire pour analyser les lignes groupées, mais cela ne devrait pas être perceptible lors du traitement en temps réel de chaque groupe.

(Au cas où vous vous le demanderiez, il s’agit d’un programme de généalogie et c’est peut-être la dernière étape pour lui permettre de charger toutes les données relatives à un million de personnes dans un espace adresse 32 bits en moins de 30 secondes. Il me reste donc un tampon de 20 secondes pour ajouter les index dans les données, ce qui sera nécessaire pour permettre l’affichage et la modification des données.)

Était-ce utile?

La solution

Vous m'avez personnellement demandé de répondre à votre question ici. Je ne connais pas la raison précise pour laquelle vous constatez une utilisation aussi importante de la mémoire, mais vous devez vous rappeler que TStringList fait bien plus que charger votre fichier. Chacune de ces étapes nécessite de la mémoire pouvant entraîner une fragmentation de la mémoire. TStringList doit charger votre fichier en mémoire, le convertir d’Ansi en Unicode, le diviser en une chaîne pour chaque ligne et insérer ces lignes dans un tableau qui sera réalloué à plusieurs reprises.

Ma question est la suivante: pourquoi utilisez-vous TStringList? Le fichier doit-il vraiment être stocké en mémoire sous forme de lignes séparées? Allez-vous modifier le fichier en mémoire ou simplement en afficher certaines parties? Garder le fichier en mémoire sous la forme d'une grosse unité et numériser le tout avec des expressions régulières qui correspondent aux parties souhaitées aura une efficacité mémoire supérieure à celle du stockage de lignes séparées.

De plus, le fichier entier doit-il être converti au format Unicode? Bien que votre application soit au format Unicode, votre fichier est Ansi. Ma recommandation générale est de convertir l'entrée Ansi au format Unicode dès que possible, car cela économise les cycles de processeur. Mais lorsque vous disposez de 320 Mo de données Ansi qui resteront sous la forme de données Ansi, la consommation de mémoire sera le goulot d'étranglement. Essayez de garder le fichier en tant que Ansi en mémoire et convertissez uniquement les parties que vous afficherez sous le nom Ansi.

Si le fichier de 320 Mo n'est pas un fichier de données dont vous extrayez certaines informations, mais qu'un ensemble de données que vous souhaitez modifier, envisagez de le convertir en une base de données relationnelle et laissez le moteur de base de données se soucier de la gestion de l'énorme ensemble de données avec RAM limitée.

Autres conseils

Et si vous faisiez votre disque original utiliser AnsiString? Cela le réduit en deux immédiatement? Le fait que Delphi utilise par défaut UnicodeString ne signifie pas que vous deviez l’utiliser.

De plus, si vous connaissez exactement la longueur de chaque chaîne (au sein d'un ou deux caractères), il est peut-être préférable d'utiliser des chaînes courtes, voire même de supprimer quelques octets supplémentaires.

Je suis curieux de savoir s’il existe une meilleure façon d’accomplir ce que vous essayez de faire. Charger 320 Mo de texte en mémoire n'est peut-être pas la meilleure solution, même si vous ne pouvez en réduire que 320 Mo

  

J'utilise Delphi 2009 et le fichier est ANSI, mais il est converti en Unicode lors de la lecture. Vous pouvez donc dire que le texte converti a une taille de 48 Mo.

Désolé, mais je ne comprends pas du tout. Si vous avez besoin que votre programme soit au format Unicode, le fichier en cours de lecture doit être "ANSI". (il doit avoir un jeu de caractères, comme WIN1252 ou ISO8859_1) n’est pas la bonne chose. Je le convertirais d'abord en UTF8. Si le fichier ne contient aucun caractère > = 128, il ne changera rien (il aura même la même taille), mais vous êtes prêt pour l'avenir.

Vous pouvez maintenant le charger dans des chaînes UTF8, ce qui ne doublera pas votre consommation de mémoire. La conversion à la volée des quelques chaînes pouvant être visibles à l'écran en même temps que la chaîne Unicode de Delphi sera plus lente, mais étant donné l'encombrement mémoire réduit, votre programme fonctionnera beaucoup mieux sur les systèmes avec peu (gratuit) mémoire.

Maintenant, si votre programme utilise toujours trop de mémoire avec TStringList, vous pouvez toujours utiliser TStrings ou même IStrings dans votre programme et écrire une classe qui implémente IStrings ou hérite de TStrings et ne conserve pas toutes les lignes en mémoire. Quelques idées qui me viennent à l’esprit:

  1. Lisez le fichier dans un TMemoryStream et conservez un tableau de pointeurs sur les premiers caractères des lignes. Il est facile de renvoyer une chaîne. Il suffit donc de renvoyer une chaîne appropriée entre le début de la ligne et le début de la suivante, en supprimant CR et NL.

  2. Si cela consomme encore trop de mémoire, remplacez TMemoryStream par un TFileStream et ne maintenez pas un tableau de pointeurs de caractères, mais un tableau de décalages de fichiers pour la ligne démarre.

  3. Vous pouvez également utiliser les fonctions de l'API Windows pour les fichiers mappés en mémoire. Cela vous permet de travailler avec des adresses de mémoire plutôt que des décalages de fichiers, mais ne consomme pas autant de mémoire que la première idée.

Par défaut, TStringList de Delphi 2009 lit un fichier au format ANSI, sauf s'il existe une marque d'ordre d'octet identifiant le fichier comme étant différent, ou si vous spécifiez un codage comme second paramètre facultatif de LoadFromFile.

Donc, si vous voyez que TStringList utilise plus de mémoire que vous ne le pensez, il se passe autre chose.

Êtes-vous par hasard en train de compiler le programme avec les sources FastMM de sourceforge et avec FullDebugMode défini? Dans ce cas, FastMM ne libère pas vraiment les blocs de mémoire inutilisés, ce qui expliquerait le problème.

Comptez-vous sur Windows pour vous dire combien de mémoire le programme utilise? Il est notoire de surestimer la mémoire utilisée par une application Delphi.

Toutefois, votre code utilise beaucoup de mémoire supplémentaire.

Votre structure d’enregistrement est de 20 octets - s’il ya un enregistrement par ligne, vous obtenez plus de données pour les enregistrements que pour le texte.

De plus, une chaîne a un temps système inhérent de 4 octets, soit 25% supplémentaires.

Je pense qu'il existe une certaine granularité d'allocation dans la gestion du segment de mémoire de Delphi, mais je ne me souviens pas de son contenu actuel. Même à 8 octets (deux pointeurs pour une liste chaînée de blocs libres), vous obtenez 25% supplémentaires.

Notez que nous avons déjà dépassé les 150% d'augmentation.

Une partie pourrait être l’algorithme d’allocation de blocs. À mesure que votre liste s'allonge, la quantité de mémoire allouée à chaque bloc augmente. Je ne l'ai pas regardé depuis longtemps, mais je pense que cela revient à doubler le montant alloué pour la dernière fois chaque fois qu'il manque de mémoire. Lorsque vous commencez à gérer des listes aussi volumineuses, vos allocations sont également beaucoup plus importantes que nécessaire.

MODIFIER - Comme l’a souligné lkessler, cette augmentation n’est en réalité que de 25%, mais elle devrait tout de même être considérée comme une partie du problème. si vous vous trouvez juste au-delà du point de basculement, il pourrait y avoir un énorme bloc de mémoire alloué à la liste qui n'est pas utilisé.

Pourquoi chargez-vous cette quantité de données dans une liste TStringList? La liste elle-même aura des frais généraux. Peut-être que TTextReader pourrait vous aider.

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