Allocateurs de segments de mémoire personnalisés


9

La plupart des programmes peuvent être assez désinvoltes au sujet de l'allocation de tas, même dans la mesure où les langages de programmation fonctionnels préfèrent allouer de nouveaux objets plutôt que de modifier les anciens, et laisser le garbage collector s'inquiéter de libérer des choses.

Dans la programmation intégrée, le secteur silencieux, cependant, il existe de nombreuses applications où vous ne pouvez pas du tout utiliser l'allocation de tas, en raison de contraintes de mémoire et de temps réel; le nombre d'objets de chaque type qui seront traités fait partie de la spécification et tout est alloué statiquement.

La programmation des jeux (au moins avec les jeux qui ont l'ambition de pousser le matériel) se situe parfois entre les deux: vous pouvez utiliser l'allocation dynamique, mais il y a suffisamment de mémoire et des contraintes logicielles en temps réel que vous ne pouvez pas traiter l'allocateur comme une boîte noire , et encore moins utiliser la récupération de place, vous devez donc utiliser des allocateurs personnalisés. C'est l'une des raisons pour lesquelles le C ++ est encore largement utilisé dans l'industrie des jeux; il vous permet de faire des choses comme http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2271.html

Quels autres domaines se trouvent dans ce territoire intermédiaire? Où, en dehors des jeux, les allocateurs personnalisés sont-ils largement utilisés?


1
Certains systèmes d'exploitation utilisent un allocateur de dalles qui fournit la mise en cache des objets mais peut également être utilisé pour réduire les échecs de conflits de cache du processeur en mappant les membres d'un objet à différents ensembles pour un cache indexé modulo 2 ** N (à la fois en ayant plusieurs instances dans une mémoire contiguë et par rembourrage variable dans la dalle). Le comportement du cache peut être plus important que l'allocation / la vitesse libre ou l'utilisation de la mémoire dans certains cas.
Paul A. Clayton

Réponses:


4

Chaque fois que vous avez une application dont le chemin critique est gourmand en performances, vous devez vous préoccuper de la façon dont vous traitez la mémoire. La plupart des applications côté client de l'utilisateur final n'entrent pas dans cette catégorie car elles sont principalement pilotées par les événements et la plupart des événements proviennent d'interactions avec l'utilisateur, et cela n'a pas autant (voire pas du tout) de contraintes de performances.

Cependant, beaucoup de logiciels back-end devraient se concentrer sur la façon dont la mémoire est gérée car beaucoup de ces logiciels peuvent évoluer pour gérer un nombre plus élevé de clients, un plus grand nombre de transactions, plus de sources de données .... Une fois que vous démarrez repoussant les limites, vous pouvez commencer à analyser la façon dont vos utilisateurs de logiciels mémorisent et écrire des schémas d'allocation personnalisés adaptés à votre logiciel plutôt que de compter sur un allocateur de mémoire complètement générique qui a été écrit pour gérer tout cas d'utilisation imaginable.

Pour vous donner quelques exemples ... dans ma première entreprise, j'ai travaillé sur un progiciel Historian, un logiciel responsable de la collecte / stockage / archivage des données de contrôle de processus (pensez à une usine, une centrale nucléaire ou une raffinerie de pétrole avec 10 millions de capteurs, nous stockons ces données). Chaque fois que nous analysions un goulot d'étranglement des performances qui empêchait l'Historian de traiter davantage de données, la plupart du temps, le problème était dans la façon dont la mémoire était gérée. Nous avons fait de grands efforts pour nous assurer que malloc / free n'était pas appelé, sauf si c'était absolument nécessaire.

Dans mon travail actuel, je travaille sur l'enregistreur numérique de vidéosurveillance et le package d'analyse. À 30 ips, chaque canal reçoit une trame vidéo toutes les 33 millisecondes. Sur le matériel que nous vendons, nous pouvons facilement enregistrer 100 canaux de vidéo. C'est donc un autre cas pour s'assurer que dans le chemin critique (appel réseau => composants de capture => logiciel de gestion de l'enregistreur => composants de stockage => disque) il n'y a pas d'allocations de mémoire dynamique. Nous avons un allocateur de trames personnalisé, qui contient des compartiments de taille fixe de tampons et utilise LIFO pour réutiliser les tampons précédemment alloués. Si vous avez besoin de 600 Ko de stockage, vous pourriez vous retrouver avec un tampon de 1024 Ko, ce qui gaspille de l'espace, mais parce qu'il est spécialement conçu pour notre utilisation où chaque allocation est de très courte durée, cela fonctionne très bien car le tampon est utilisé,

Dans le type d'applications que j'ai décrit (déplacer de nombreuses données de A vers B et gérer un grand nombre de demandes des clients), aller vers le tas et revenir est une source majeure de goulots d'étranglement des performances du processeur. Garder la fragmentation des segments au minimum est un avantage secondaire, mais pour autant que je sache, la plupart des systèmes d'exploitation modernes implémentent déjà des segments à faible fragmentation (au moins, je sais que Windows le fait, et j'espère que d'autres le feront aussi). Personnellement, depuis plus de 12 ans travaillant dans ces types d'environnements, j'ai vu des problèmes d'utilisation du processeur liés au tas assez fréquemment, alors que je n'ai jamais vu un système qui souffrait réellement d'un tas fragmenté.


"Nous avons fait de grands efforts pour nous assurer que malloc / free n'était pas appelé à moins que cela ne soit absolument nécessaire ..." - Je connais des gars du matériel qui construisent des routeurs. Ils ne s'en soucient même pas malloc/free. Ils réservent un bloc de mémoire et l'utilisent comme structure de données de curseur. La plupart de leur travail s'est réduit à garder une trace des index.

4

Traitement vidéo, effets visuels, systèmes d'exploitation, etc. Souvent, cependant, les gens en abusent. Il n'est pas nécessaire de séparer la structure des données et l'allocateur pour obtenir une allocation efficace.

Par exemple, cela introduit beaucoup de complexité supplémentaire pour diviser l'allocation efficace des nœuds d'arbre dans un octree loin de l'octree lui-même et s'appuyer sur un allocateur externe. Ce n'est pas nécessairement une violation de SRP que de fusionner ces deux préoccupations et de faire en sorte que l'octree ait la responsabilité d'allouer plusieurs nœuds à la fois de manière contiguë, car cela n'augmente pas le nombre de raisons de changer. Elle peut, pratiquement parlant, la diminuer.

En C ++, par exemple, l'un des effets secondaires retardés de l'utilisation de conteneurs standard sur un allocateur externe a rendu les structures liées similaires std::mapet std::listconsidérées comme presque inutiles par la communauté C ++, car elles les comparent àstd::allocatortandis que ces structures de données allouent un nœud à la fois. Bien sûr, vos structures liées vont mal fonctionner dans ce cas, mais les choses auraient tourné tellement différemment si l'allocation efficace des nœuds pour les structures liées était considérée comme la responsabilité d'une structure de données plutôt que celle d'un allocateur. Ils peuvent toujours utiliser une allocation personnalisée pour d'autres raisons comme le suivi / profilage de la mémoire, mais s'appuyer sur l'allocateur pour rendre les structures liées efficaces tout en essayant d'allouer les nœuds un par un les rend tous, par défaut, extrêmement inefficaces, ce qui serait bien s'il venait avec une mise en garde bien connue selon laquelle les structures liées ont maintenant besoin d'un allocateur personnalisé, comme la liste gratuite, pour être raisonnablement efficace et éviter de déclencher des erreurs de cache à gauche et à droite. Beaucoup plus pratique pourrait être quelque chose commestd::list<T, BlockSize, Alloc>, où BlockSizeindique le nombre de nœuds contigus à allouer à la fois pour la liste libre (la spécification de 1 conduirait effectivement à std::listce qu'elle est maintenant).

Mais il n'y a pas une telle mise en garde, ce qui conduit alors à toute une communauté de blockheads faisant écho à un mantra culte que les listes liées sont inutiles, par exemple


3

Un autre domaine dans lequel vous souhaiterez peut-être un allocateur personnalisé est d'empêcher la fragmentation du segment de mémoire . Au fil du temps, votre tas peut allouer de petits objets fragmentés dans le tas. Si votre programme ne peut pas garder la mémoire de tas ensemble, lorsque votre programme va allouer un objet plus grand, il doit réclamer plus de mémoire du système car il ne peut pas trouver un bloc libre entre votre tas fragmenté existant (trop petit des objets gênent). L'utilisation totale de la mémoire de votre programme augmentera avec le temps et vous consommerez des pages supplémentaires de mémoire inutilement. C'est donc un gros problème pour les programmes qui devraient s'exécuter sur de longues périodes (pensez aux bases de données, aux serveurs, etc.).

Où, en dehors des jeux, les allocateurs personnalisés sont-ils largement utilisés?

Facebook

Découvrez jemalloc que Facebook commence à utiliser pour améliorer ses performances de tas et réduire la fragmentation.


Droite. Cependant, un ramasse-miettes à copier résout proprement le problème de la fragmentation, n'est-ce pas?
rwallace
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.