J'ai conçu par le passé un gestionnaire de stockage en bloc fixe (SM) et un gestionnaire de mémoire à usage général . Dans les deux cas, j'alloue une grande partie de la mémoire du tas au démarrage et je réutilise encore et encore la mémoire désallouée, évitant les appels fréquents aux coûteux appels malloc / new .
Si je parle de Bloc fixe SM ( lien Github) , alors j'ai pratiquement vu le bénéfice de performance qu'il apporte. Dans mon cas, il s'agissait d'une amélioration d'environ 40% pour les allocations de taille aléatoire.
Mais dans le cas d'un gestionnaire de mémoire générique ( lien Github ) (n'ayant pas de pools de mémoire), je n'ai vu aucun gain de performance visible. Le seul gain que j'ai pu voir était l'accès aux statistiques d'utilisation de la mémoire. En termes de performances, cela devient plus lent en raison de la surcharge liée à la détermination des blocs libres (pendant l'allocation) et de l'emplacement de la mémoire dans la carte (pendant la désallocation).
Ma question est donc la suivante: dans quel scénario un allocateur de mémoire à usage général personnalisé serait-il utile? Cela en vaut-il la peine?
3 Réponses :
Les performances ne sont pas la seule raison de développer un allocateur personnalisé. D'autres raisons peuvent inclure:
Il est rarement nécessaire de concevoir un gestionnaire de mémoire personnalisé. Il y en a tellement que la plupart des gens peuvent en trouver un à utiliser sur le support. Il y a quelques années, j'avais un système C ++ qui comprenait un interpréteur. Lors des premiers tests, ce n'était pas aussi rapide que nous l'avions espéré. Le profilage a montré que le problème était l'allocation de mémoire et il s'est manifesté dans la classe de chaîne. Nous avons téléchargé environ deux douzaines de gestionnaires de mémoire sur Internet et avons essayé chacun d'eux à tour de rôle. Nous avons pu obtenir une amélioration massive de la vitesse. Le gestionnaire de mémoire, nous avons fini par utiliser des blocs toujours alloués dans des tailles qui étaient des puissances de 2 et maintenait des pools séparés pour chaque taille de bloc.
Nous avons trouvé plus de gestionnaires de mémoire que nous ne pourrions en tester.
Même j'ai remarqué l'augmentation des performances lorsque j'ai utilisé le gestionnaire de mémoire de bloc fixe (ayant différents pools pour chaque taille de bloc).
Je soupçonne que la plupart des gestionnaires de mémoire qui ont utilisé des tailles de bloc fixes découlent d'analyses académiques des algorithmes de premier ajustement et de meilleur ajustement.
Généralement, la plus grande motivation pour créer un allocateur personnalisé n'a rien à voir avec les performances. Au lieu de cela, les programmes qui doivent fonctionner sur une grande variété de systèmes se retrouvent dans des bibliothèques incohérentes et / ou incomplètes. La bibliothèque std en particulier. De plus, les limitations matérielles des systèmes embarqués exigent souvent que les applications fassent très attention à l'utilisation et à la gestion de la mémoire. Ces programmes exécutent tout, de la climatisation de votre voiture au pilote automatique des avions. Il existe également des systèmes qui n'ont pas de pile nécessitant une gestion minutieuse du tas. Ces exemples ne sont que la pointe de l'iceberg. Ajoutez à cela un allocateur de base, comme l'une des nombreuses conceptions basées sur la pile, et vous obtenez une solution très simple à écrire, facile à entretenir et hautement performante. Une autre raison d'écrire votre propre allocateur est tout simplement de manquer de mémoire. Celui-ci apparaît généralement lorsqu'il y a beaucoup de mémoire "libre", mais pas de blocs assez grands pour remplir une requête. Cela signifie que la mémoire est devenue fragmentée au sein de l'allocateur. Google "allocateur de mémoire facebook" pour un exemple.
Le seul thème commun à tous ces programmes est le besoin d'un gestionnaire de mémoire spécifique à une solution. C'est la raison pour laquelle il y a tant de gestionnaires disponibles gratuitement. S'il n'y a pas de besoin spécifique, il n'y a aucune bonne raison d'utiliser autre chose que malloc / new. Ou, mieux encore, établir une stratégie qui nécessite l'utilisation de pointeurs intelligents qui gèrent les allocations de mémoire en arrière-plan. Le profilage de la mémoire et la statistique se font facilement avec un outil externe et constituent généralement la première étape que les programmeurs prennent sur le chemin d'un gestionnaire de mémoire spécifique à une solution. Vous ne devriez jamais commencer avec un "gestionnaire de mémoire générique" et demander plus tard "pourquoi j'utilise ceci".
Enfin, votre utilisation du terme «gestionnaire de stockage» fait référence à une classe beaucoup plus large d'outils qui gèrent généralement les données de la mémoire à court terme au stockage et à l'archivage à long terme. Ces outils sont beaucoup plus rares, peuvent être très complexes et, encore une fois, sont un outil spécifique à une solution qui doit être utilisé pour des besoins spécifiques tels qu'une base de données.
Les performances sont généralement la dernière raison pour inclure un gestionnaire de mémoire dans un programme, cela étant dit, si un gestionnaire de mémoire ne peut pas surpasser le système d'au moins un facteur de 2, alors quelque chose ne va vraiment pas. Soit le programme a trouvé un cas dégénéré pour le gestionnaire, soit le gestionnaire lui-même a un problème qui doit être résolu.
Oui, ça l'est. Si vous pouvez équilibrer vitesse, simplicité et portabilité dans un gestionnaire de mémoire, les avantages l'emportent largement sur le travail.
malloc ()
etnew ()
ne sont pas des appels système. Les éléments de niveau inférieur commemmap
etbrk ()
le sont.@ JL2210 Et même dans ce cas, les appels effectués à partir de code C ou C ++ sont en fait des appels de fonction qui encapsulent les appels système sous-jacents, qui n'ont même pas besoin d'être appelés
mmap () code > ou
brk ()
. Par exemple, l 'open ()
"appel système" est une fonction qui peut encapsuler unopenat ()
appel système . Personnellement, je déteste la division artificielle et trompeuse des fonctions de la bibliothèque en «fonctions» et «appels système».Êtes-vous en train de poser des questions sur les applications potentielles de cette implémentation particulière (assez naïve, IMO), ou sur les applications potentielles d'un gestionnaire de mémoire personnalisé abstrait conçu à des fins spéciales?
@AndrewHenle Correct (bien que le nom de la fonction soit
brk ()
, pasbreak ()
).@ JL2210 bien que le nom de la fonction soit
brk ()
, pasbreak()
Corrigé. Trop tôt le matin ... ;-) (et le commentaire était plus pour les autres ...)@IgorG Je crois que si nous concevons un gestionnaire de mémoire dans un but très spécifique, par exemple si nous avons fréquemment des demandes d'allocation pour un objet particulier, la création d'un gestionnaire de mémoire pourrait être bénéfique. Mais ma question concerne un gestionnaire de mémoire à usage général.
@AndrewHenle, eh bien, il est bon de savoir si la fonction provient d'une bibliothèque ou du système. L'appel à la bibliothèque est bon marché (espace utilisateur), l'appel au noyau pourrait être plus cher. On peut s'attendre à ce que les fonctions de la bibliothèque soient conformes au standard C (ce qui implique une meilleure portabilité), tandis que les appels au noyau sont soumis à POSIX, UEFI ou à toute autre norme spécifique au système d'exploitation qui s'applique.
@ JL2210 Merci pour votre contribution. Mise à jour de ma question pour supprimer les mots «appel système».
@IgorG il est bon de savoir si la fonction provient d'une bibliothèque ou du système Mais il n'y a pas de mappage direct des appels de fonction aux appels système. Dites-moi, combien "d'appels système" entraînera un "appel de fonction" standard C à
printf ()
? Si vous craignez que les appels au noyau soient plus chers, vous vous engagez dans une optimisation prématurée. Et la distinction «appel système» vs «appel de fonction» est de toute façon inutile pour déterminer cela.@AndrewHenle, je ne sais pas combien d'appels système sont effectués par printf (et franchement, ce n'est pas si important, avec le blocage des E / S étant de loin un facteur de limitation de temps beaucoup plus important ici). Mais quand même, dans le contexte de cette question, savoir qu'un allocateur de pool d'espace utilisateur par thread ne provoquerait ni appel de noyau ni blocage de threads est une bonne chose.