J'ai entendu le terme "fragmentation de la mémoire" utilisé plusieurs fois dans le contexte de l'allocation dynamique de mémoire C ++. J'ai trouvé quelques questions sur la façon de gérer la fragmentation de la mémoire, mais je ne trouve pas de question directe qui la traite elle-même. Alors:
- Qu'est-ce que la fragmentation de la mémoire?
- Comment savoir si la fragmentation de la mémoire est un problème pour mon application? Quel type de programme est le plus susceptible de souffrir?
- Quels sont les bons moyens courants de gérer la fragmentation de la mémoire?
Aussi:
- J'ai entendu dire que l'utilisation d'allocations dynamiques pouvait augmenter considérablement la fragmentation de la mémoire. Est-ce vrai? Dans le contexte de C ++, je comprends que tous les conteneurs standard (std :: string, std :: vector, etc.) utilisent l'allocation dynamique de mémoire. Si ceux-ci sont utilisés dans un programme (en particulier std :: string), la fragmentation de la mémoire est-elle plus susceptible d'être un problème?
- Comment gérer la fragmentation de la mémoire dans une application lourde en STL?
c++
memory
heap
fragmentation
AshleysBrain
la source
la source
Réponses:
Imaginez que vous ayez une "grande" étendue (32 octets) de mémoire libre:
Maintenant, allouez-en une partie (5 allocations):
Maintenant, libérez les quatre premières allocations mais pas la cinquième:
Maintenant, essayez d'allouer 16 octets. Oups, je ne peux pas, même s'il y en a presque le double gratuitement.
Sur les systèmes dotés de mémoire virtuelle, la fragmentation pose moins de problèmes que vous ne le pensez, car les grandes allocations doivent uniquement être contiguës dans l' espace d'adressage virtuel , pas dans l' espace d'adressage physique . Donc, dans mon exemple, si j'avais de la mémoire virtuelle avec une taille de page de 2 octets, je pourrais faire mon allocation de 16 octets sans problème. La mémoire physique ressemblerait à ceci:
tandis que la mémoire virtuelle (étant beaucoup plus grande) pourrait ressembler à ceci:
Le symptôme classique de la fragmentation de la mémoire est que vous essayez d'allouer un gros bloc et vous ne pouvez pas, même si vous semblez avoir suffisamment de mémoire disponible. Une autre conséquence possible est l'incapacité du processus à libérer de la mémoire sur le système d'exploitation (car il y a encore un objet en cours d'utilisation dans tous les blocs qu'il a alloués à partir du système d'exploitation, même si ces blocs sont maintenant pour la plupart inutilisés).
Tactiques pour empêcher la fragmentation de la mémoire dans le travail C ++ en allouant des objets de différentes zones en fonction de leur taille et / ou de leur durée de vie attendue. Donc, si vous allez créer beaucoup d'objets et les détruire tous ensemble plus tard, allouez-les à partir d'un pool de mémoire. Toutes les autres allocations que vous effectuez entre elles ne proviendront pas du pool, donc ne seront pas situées entre elles en mémoire, de sorte que la mémoire ne sera pas fragmentée en conséquence.
En règle générale, vous n'avez pas à vous en préoccuper, à moins que votre programme ne dure longtemps et fasse beaucoup d'allocation et de libération. C'est lorsque vous avez des mélanges d'objets de courte durée et de longue durée de vie que vous êtes le plus à risque, mais même alors,
malloc
il fera de son mieux pour vous aider. Fondamentalement, ignorez-le jusqu'à ce que votre programme ait des échecs d'allocation ou que le système manque de mémoire de manière inattendue (remarquez cela dans les tests, de préférence!).Les bibliothèques standard ne sont pas pires qu'autre chose qui alloue de la mémoire, et les conteneurs standard ont tous un
Alloc
paramètre de modèle que vous pouvez utiliser pour affiner leur stratégie d'allocation si cela est absolument nécessaire.la source
ffffffffffffffff
est une allocation contiguë dans la mémoire virtuelle, mais aucune telle allocation contiguë ne peut exister dans la mémoire physique. Si vous préférez le voir comme ils sont également fragmentés, mais l'espace virtuel est beaucoup plus grand, alors n'hésitez pas à le regarder de cette façon. Le point pratique important est que l'utilisation de vastes espaces d'adressage virtuels est souvent suffisante pour pouvoir ignorer la fragmentation, donc cela m'aide chaque fois qu'il me permet de faire mon allocation de 16 octets.La fragmentation de la mémoire se produit lorsque la majeure partie de votre mémoire est allouée dans un grand nombre de blocs non contigus, ou morceaux - laissant un bon pourcentage de votre mémoire totale non allouée, mais inutilisable pour la plupart des scénarios typiques. Cela entraîne des exceptions de mémoire insuffisante ou des erreurs d'allocation (c.-à-d. Malloc renvoie null).
La façon la plus simple d'y penser est d'imaginer que vous avez un grand mur vide sur lequel vous devez mettre des images de tailles différentes . Chaque image prend une certaine taille et vous ne pouvez évidemment pas la diviser en petits morceaux pour l'adapter. Vous avez besoin d'un endroit vide sur le mur, de la taille de l'image, sinon vous ne pouvez pas le mettre en place. Maintenant, si vous commencez à accrocher des images sur le mur et que vous ne faites pas attention à la façon dont vous les organisez, vous vous retrouverez bientôt avec un mur partiellement recouvert d'images et même si vous avez des espaces vides, la plupart des nouvelles images ne s'adapteront pas car ils sont plus grands que les spots disponibles. Vous pouvez toujours accrocher de très petites images, mais la plupart ne conviennent pas. Vous devrez donc réorganiser (compacter) ceux déjà sur le mur pour faire de la place pour plus ..
Maintenant, imaginez que le mur est votre (tas) mémoire et les images sont des objets .. C'est la fragmentation de la mémoire ..
Comment savoir si la fragmentation de la mémoire est un problème pour mon application? Quel type de programme est le plus susceptible de souffrir?
Un signe révélateur que vous pouvez être confronté à une fragmentation de la mémoire est que si vous obtenez de nombreuses erreurs d'allocation, en particulier lorsque le pourcentage de mémoire utilisée est élevé - mais que vous n'avez pas encore utilisé toute la mémoire -, techniquement, vous devriez avoir beaucoup de place pour les objets que vous essayez d'allouer.
Lorsque la mémoire est fortement fragmentée, les allocations de mémoire prendront probablement plus de temps car l'allocateur de mémoire doit faire plus de travail pour trouver un espace approprié pour le nouvel objet. Si, à son tour, vous disposez de nombreuses allocations de mémoire (ce que vous faites probablement depuis que vous vous êtes retrouvé avec une fragmentation de la mémoire), le temps d'allocation peut même provoquer des retards notables.
Quels sont les bons moyens courants de gérer la fragmentation de la mémoire?
Utilisez un bon algorithme pour allouer de la mémoire. Au lieu d'allouer de la mémoire à un grand nombre de petits objets, pré-allouez de la mémoire à un tableau contigu de ces petits objets. Parfois, un peu de gaspillage lors de l'allocation de mémoire peut favoriser les performances et vous éviter d'avoir à gérer la fragmentation de la mémoire.
la source
La fragmentation de la mémoire est le même concept que la fragmentation du disque: elle se réfère au gaspillage d'espace car les zones utilisées ne sont pas suffisamment rapprochées.
Supposons pour un exemple de jouet simple que vous disposez de dix octets de mémoire:
Allouons maintenant trois blocs de trois octets, nommés A, B et C:
Désallouez maintenant le bloc B:
Maintenant, que se passe-t-il si nous essayons d'allouer un bloc de quatre octets D? Eh bien, nous avons quatre octets de mémoire libres, mais nous n'avons pas quatre octets contigus de mémoire libres, donc nous ne pouvons pas allouer D! Il s'agit d'une utilisation inefficace de la mémoire, car nous aurions dû pouvoir stocker D, mais nous n'avons pas pu. Et nous ne pouvons pas déplacer C pour faire de la place, car très probablement certaines variables de notre programme pointent vers C, et nous ne pouvons pas trouver et modifier automatiquement toutes ces valeurs.
Comment savez-vous que c'est un problème? Eh bien, le plus grand signe est que la taille de la mémoire virtuelle de votre programme est considérablement plus grande que la quantité de mémoire que vous utilisez réellement. Dans un exemple concret, vous auriez beaucoup plus de dix octets de mémoire, donc D serait simplement alloué à partir d'un octet 9, et les octets 3-5 resteraient inutilisés sauf si vous allouiez plus tard quelque chose de trois octets de long ou plus petit.
Dans cet exemple, 3 octets ne sont pas beaucoup à perdre, mais considérons un cas plus pathologique où deux allocations d'un couple d'octets sont, par exemple, dix mégaoctets en mémoire, et vous devez allouer un bloc de taille 10 mégaoctets + 1 octet. Vous devez aller demander au système d'exploitation plus de dix mégaoctets de mémoire virtuelle pour le faire, même si vous n'êtes qu'un octet de moins d'avoir déjà suffisamment d'espace.
Comment l'empêchez-vous? Les pires cas ont tendance à se produire lorsque vous créez et détruisez fréquemment de petits objets, car cela a tendance à produire un effet de "fromage suisse" avec de nombreux petits objets séparés par de nombreux petits trous, ce qui rend impossible d'allouer des objets plus gros dans ces trous. Lorsque vous savez que vous allez le faire, une stratégie efficace consiste à pré-allouer un grand bloc de mémoire en tant que pool pour vos petits objets, puis à gérer manuellement la création des petits objets dans ce bloc, plutôt que de laisser l'allocateur par défaut le gère.
En général, moins vous faites d'allocations, moins la mémoire est susceptible d'être fragmentée. Cependant, STL gère cela de manière assez efficace. Si vous avez une chaîne qui utilise l'intégralité de son allocation actuelle et que vous y ajoutez un caractère, elle ne se réalloue pas simplement à sa longueur actuelle plus un, elle double sa longueur. Il s'agit d'une variante de la stratégie du «pool pour les petites allocations fréquentes». La chaîne saisit une grande partie de la mémoire afin de pouvoir gérer efficacement les petites augmentations de taille répétées sans effectuer de petites réallocations répétées. En fait, tous les conteneurs STL font ce genre de chose, donc généralement vous n'aurez pas à vous soucier trop de la fragmentation causée par la réallocation automatique des conteneurs STL.
Bien que, bien sûr, les conteneurs STL ne regroupent pas la mémoire entre eux, donc si vous allez créer de nombreux petits conteneurs (plutôt que quelques conteneurs qui sont redimensionnés fréquemment), vous devrez peut-être vous préoccuper de prévenir la fragmentation de la même manière que vous serait pour tous les petits objets fréquemment créés, STL ou non.
la source
La fragmentation de la mémoire est le problème de la mémoire devenant inutilisable même si elle est théoriquement disponible. Il existe deux types de fragmentation: la fragmentation interne est une mémoire qui est allouée mais ne peut pas être utilisée (par exemple, lorsque la mémoire est allouée en blocs de 8 octets mais que le programme fait à plusieurs reprises des allocations uniques alors qu'il n'a besoin que de 4 octets). la fragmentation externe est le problème de la mémoire libre se divisant en plusieurs petits morceaux de sorte que les demandes d'allocation importantes ne peuvent pas être satisfaites bien qu'il y ait suffisamment de mémoire libre globale.
la fragmentation de la mémoire est un problème si votre programme utilise beaucoup plus de mémoire système que ne l'exigent ses données de paylod réelles (et vous avez exclu les fuites de mémoire).
Utilisez un bon allocateur de mémoire. IIRC, ceux qui utilisent une stratégie de «meilleur ajustement» sont généralement beaucoup plus efficaces pour éviter la fragmentation, si un peu plus lentement. Cependant, il a également été démontré que pour toute stratégie d'allocation, il existe des pires cas pathologiques. Heureusement, les modèles d'allocation typiques de la plupart des applications sont en fait relativement bénins à gérer pour les allocateurs. Il y a un tas d'articles si vous êtes intéressé par les détails:
la source
Mise à jour:
Google TCMalloc: Discussion-caching Malloc
Il a été constaté que il est assez bon à la manipulation fragmentation dans un processus en cours d' exécution longue.
J'ai développé une application serveur qui a rencontré des problèmes de fragmentation de la mémoire sur HP-UX 11.23 / 11.31 ia64.
Cela ressemblait à ça. Il y avait un processus qui a fait des allocations de mémoire et des désallocations et a fonctionné pendant des jours. Et même s'il n'y a eu aucune fuite de mémoire, la consommation de mémoire du processus a continué d'augmenter.
À propos de mon expérience. Sur HP-UX, il est très facile de trouver la fragmentation de la mémoire à l'aide de HP-UX gdb. Vous définissez un point d'arrêt et lorsque vous l'atteignez, vous exécutez cette commande:
info heap
et voyez toutes les allocations de mémoire pour le processus et la taille totale du tas. Ensuite, vous continuez votre programme et puis quelque temps plus tard, vous atteignez à nouveau le point d'arrêt. Vous recommencezinfo heap
. Si la taille totale du tas est plus grande mais que le nombre et la taille des allocations distinctes sont les mêmes, il est probable que vous ayez des problèmes d'allocation de mémoire. Si nécessaire, effectuez cette vérification quelques fois à l'avance.Ma façon d'améliorer la situation était la suivante. Après avoir effectué une analyse avec HP-UX gdb, j'ai vu que les problèmes de mémoire étaient dus au fait que j'utilisais
std::vector
pour stocker certains types d'informations à partir d'une base de données.std::vector
exige que ses données soient conservées dans un seul bloc. J'avais quelques conteneurs basés surstd::vector
. Ces conteneurs ont été recréés régulièrement. Il y avait souvent des situations où de nouveaux enregistrements étaient ajoutés à la base de données et ensuite les conteneurs étaient recréés. Et comme les conteneurs recréés étaient plus gros, ils ne correspondaient pas aux blocs de mémoire libre disponibles et le moteur d'exécution a demandé un nouveau bloc plus gros à partir du système d'exploitation. Par conséquent, même s'il n'y a eu aucune fuite de mémoire, la consommation de mémoire du processus a augmenté. J'ai amélioré la situation lorsque j'ai changé les conteneurs. Au lieu destd::vector
j'ai commencé à utiliserstd::deque
qui a une façon différente d'allouer de la mémoire pour les données.Je sais que l'un des moyens d'éviter la fragmentation de la mémoire sur HP-UX consiste à utiliser Small Block Allocator ou MallocNextGen. Sur RedHat Linux, l'allocateur par défaut semble gérer assez bien l'allocation d'un grand nombre de petits blocs. Sous Windows, il existe
Low-fragmentation Heap
et il résout le problème du grand nombre de petites allocations.Je crois comprendre que dans une application lourde en STL, vous devez d'abord identifier les problèmes. Les allocateurs de mémoire (comme dans libc) gèrent en fait le problème de nombreuses petites allocations, ce qui est typique
std::string
(par exemple, dans mon application serveur, il y a beaucoup de chaînes STL mais, comme je le constate en exécutant,info heap
elles ne posent aucun problème). Mon impression est que vous devez éviter de fréquentes allocations importantes. Malheureusement, il y a des situations où vous ne pouvez pas les éviter et devez changer votre code. Comme je l'ai dit dans mon cas, j'ai amélioré la situation lorsque je suis passé àstd::deque
. Si vous identifiez votre fragment de mémoire, il pourrait être possible d'en parler plus précisément.la source
La fragmentation de la mémoire est plus susceptible de se produire lorsque vous allouez et désallouez de nombreux objets de tailles différentes. Supposons que vous ayez la disposition suivante en mémoire:
Désormais, une fois
obj2
libéré, vous disposez de 120 Ko de mémoire inutilisée, mais vous ne pouvez pas allouer un bloc complet de 120 Ko, car la mémoire est fragmentée.Les techniques courantes pour éviter cet effet incluent les tampons en anneau et les pools d'objets . Dans le contexte de la STL, des méthodes comme
std::vector::reserve()
peuvent aider.la source
Une réponse très détaillée sur la fragmentation de la mémoire peut être trouvée ici.
http://library.softwareverify.com/memory-fragmentation-your-worst-nightmare/
C'est l'aboutissement de 11 ans de réponses sur la fragmentation de la mémoire que j'ai fournies aux personnes qui me posaient des questions sur la fragmentation de la mémoire sur softwareverify.com
la source
Lorsque votre application utilise la mémoire dynamique, elle alloue et libère des morceaux de mémoire. Au début, tout l'espace mémoire de votre application est un bloc contigu de mémoire libre. Cependant, lorsque vous allouez et libérez des blocs de taille différente, la mémoire commence à être fragmentée , c'est-à-dire qu'au lieu d'un grand bloc libre contigu et d'un certain nombre de blocs alloués contigus, il y aura un mélange de blocs alloués et libres. Comme les blocs libres ont une taille limitée, il est difficile de les réutiliser. Par exemple, vous pouvez avoir 1000 octets de mémoire libre, mais vous ne pouvez pas allouer de mémoire pour un bloc de 100 octets, car tous les blocs libres ont une longueur maximale de 50 octets.
Une autre source de fragmentation inévitable, mais moins problématique, est que dans la plupart des architectures, les adresses mémoire doivent être alignées sur des limites d'octets de 2, 4, 8 etc. (c'est-à-dire que les adresses doivent être des multiples de 2, 4, 8, etc.). Cela signifie que même si vous avez par exemple une structure contenant 3
char
champs, votre structure peut avoir une taille de 12 au lieu de 3, car chaque champ est aligné sur une limite de 4 octets.La réponse évidente est que vous obtenez une exception de mémoire insuffisante.
Apparemment, il n'existe aucun bon moyen portable de détecter la fragmentation de la mémoire dans les applications C ++. Voir cette réponse pour plus de détails.
C'est difficile en C ++, car vous utilisez des adresses mémoire directes dans les pointeurs et vous n'avez aucun contrôle sur qui référence une adresse mémoire spécifique. Réorganiser les blocs de mémoire alloués (comme le fait le ramasse-miettes Java) n'est pas une option.
Un allocateur personnalisé peut aider en gérant l'allocation de petits objets dans un plus gros morceau de mémoire et en réutilisant les emplacements libres dans ce morceau.
la source
Il s'agit d'une version super simplifiée pour les nuls.
À mesure que les objets sont créés en mémoire, ils sont ajoutés à la fin de la partie utilisée en mémoire.
Si un objet qui n'est pas à la fin de la partie de mémoire utilisée est supprimé, ce qui signifie que cet objet se trouvait entre 2 autres objets, cela créera un "trou".
C'est ce qu'on appelle la fragmentation.
la source
Lorsque vous souhaitez ajouter un élément sur le tas, ce qui se passe, c'est que l'ordinateur doit effectuer une recherche d'espace pour s'adapter à cet élément. C'est pourquoi les allocations dynamiques lorsqu'elles ne sont pas effectuées sur un pool de mémoire ou avec un allocateur poolé peuvent "ralentir" les choses. Pour une application STL lourde si vous faites du multi-thread, il y a l' allocateur Hoard ou la version TBB Intel .
Maintenant, lorsque la mémoire est fragmentée, deux choses peuvent se produire:
la source
La fragmentation de la mémoire se produit car des blocs de mémoire de tailles différentes sont demandés. Prenons un tampon de 100 octets. Vous demandez deux caractères, puis un entier. Vous libérez maintenant les deux caractères, puis vous demandez un nouvel entier, mais cet entier ne peut pas tenir dans l'espace des deux caractères. Cette mémoire ne peut pas être réutilisée car elle n'est pas dans un bloc contigu suffisamment grand pour être réalloué. En plus de cela, vous avez invoqué de nombreux frais généraux d'allocateur pour vos caractères.
Essentiellement, la mémoire ne vient que par blocs d'une certaine taille sur la plupart des systèmes. Une fois que vous avez divisé ces blocs, ils ne peuvent pas être rejoints jusqu'à ce que le bloc entier soit libéré. Cela peut conduire à l'utilisation de blocs entiers alors qu'en réalité seule une petite partie du bloc est utilisée.
Le principal moyen de réduire la fragmentation du segment de mémoire consiste à effectuer des allocations plus importantes et moins fréquentes. À l'extrême, vous pouvez utiliser un tas géré qui est capable de déplacer des objets, au moins, dans votre propre code. Cela élimine complètement le problème - du point de vue de la mémoire, de toute façon. Évidemment, les objets en mouvement et autres ont un coût. En réalité, vous n'avez vraiment de problème que si vous allouez souvent de très petites quantités du tas. L'utilisation de conteneurs contigus (vecteur, chaîne, etc.) et l'allocation sur la pile autant que possible humainement (toujours une bonne idée de performances) est la meilleure façon de la réduire. Cela augmente également la cohérence du cache, ce qui accélère l'exécution de votre application.
Ce que vous devez retenir, c'est que sur un système de bureau 32 bits x86, vous disposez de 2 Go de mémoire, qui sont divisés en "pages" de 4 Ko (assez sûr que la taille de la page est la même sur tous les systèmes x86). Vous devrez invoquer une certaine fragmentation omgwtfbbq pour avoir un problème. La fragmentation est vraiment un problème du passé, car les tas modernes sont excessivement grands pour la grande majorité des applications, et il existe une prévalence de systèmes capables de le supporter, tels que les tas gérés.
la source
Un bon (= horrible) exemple pour les problèmes associés à la fragmentation de la mémoire a été le développement et la sortie de "Elemental: War of Magic" , un jeu informatique de Stardock .
Le jeu a été conçu pour une mémoire de 32 bits / 2 Go et a dû faire beaucoup d'optimisation dans la gestion de la mémoire pour que le jeu fonctionne avec ces 2 Go de mémoire. L'optimisation entraînant une allocation et une désallocation constantes, une fragmentation de la mémoire au fil du temps s'est produite et a entraîné le crash du jeu à chaque fois .
Il y a une interview sur "l'histoire de la guerre" sur YouTube.
la source