Exemples convaincants de la coutume, C++ allocateurs?
Ce sont vraiment de bonnes raisons d'abandonner std::allocator
en faveur d'une solution personnalisée? Avez-vous courir à travers toutes les situations où elle est absolument nécessaire pour la correction, de performances, d'évolutivité, etc? Tout vraiment intelligent exemples?
Personnalisé allocateurs ont toujours été une caractéristique de la Bibliothèque Standard que je n'ai pas eu besoin de beaucoup pour. Je me demandais juste si quelqu'un ici pourrait fournir quelques exemples convaincants pour justifier leur existence.
Vous devez vous connecter pour publier un commentaire.
Comme je le mentionne ici, j'ai vu Intel TBB personnalisé STL allocateur d'améliorer sensiblement les performances d'une application multithread, simplement en changeant un seul
à
(c'est un moyen rapide et pratique de commutation de l'allocateur d'utiliser TBB est chouette thread-privé tas; voir à la page 7 de ce document)
Un domaine où personnalisé allocateurs peut être utile, c'est le développement de jeux, notamment sur les consoles de jeu, comme ils l'ont seulement une petite quantité de mémoire et pas de swap. Sur ces systèmes, vous voulez vous assurer que vous avez un contrôle serré sur chaque sous-système, de sorte que l'on critique le système ne peut pas voler la mémoire d'une critique. D'autres choses comme la piscine allocateurs peut aider à réduire la fragmentation de la mémoire. Vous pouvez trouver un long document détaillé sur le sujet:
EASTL -- Electronic Arts Modèle Standard de la Bibliothèque
Je suis en train de travailler sur un mmap-allocateur qui permet vecteurs pour utiliser la mémoire de
un fichier mappé en mémoire. Le but est d'avoir des vecteurs que l'utilisation de stockage que
sont directement dans la mémoire virtuelle obtenue par mmap. Notre problème est de
améliorer la lecture de gros fichiers (>10 GO) dans la mémoire avec aucune copie
les frais généraux, donc j'ai besoin de cet allocateur personnalisé.
Jusqu'à présent j'ai le squelette d'un allocateur personnalisé
(qui dérive de std::allocator), je pense que c'est un bon départ
point d'écrire propre allocateurs. N'hésitez pas à utiliser ce bout de code
quelle que soit la façon dont vous le souhaitez:
Pour l'utiliser, déclarer un conteneur STL comme suit:
Il peut être utilisé par exemple pour ouvrir une session chaque fois que la mémoire est allouée. Ce qui est nécessaire
est le relier struct, sinon le vecteur contenant utilise le super-classes allouer/désallouer
des méthodes.
Mise à jour: Le mappage de la mémoire de l'allocateur est maintenant disponible à https://github.com/johannesthoma/mmap_allocator et LGPL. N'hésitez pas à utiliser pour vos projets.
Je travaille avec MySQL est un moteur de stockage qui utilise c++ pour son code. Nous sommes à l'aide d'un allocateur personnalisé à utiliser le MySQL système de mémoire plutôt qu'en concurrence avec MySQL pour la mémoire. Il permet de nous assurer que nous sommes à l'aide de la mémoire que l'utilisateur configuré MySQL à utiliser, et pas "extra".
Il peut être utile d'utiliser des allocateurs de l'utilisation d'un pool de mémoire au lieu de le tas. C'est un exemple parmi beaucoup d'autres.
Pour la plupart des cas, c'est certainement une optimisation prématurée. Mais il peut être très utile dans certains contextes (périphériques intégrés, jeux, etc).
Je n'ai pas écrit de code C++ avec une coutume STL allocateur, mais je peux imaginer un serveur web écrit en C++, qui utilise un allocateur personnalisé pour la suppression automatique des données temporaires nécessaires pour répondre à une requête HTTP. L'allocation personnalisée gratuite de toutes les données en une fois une fois la réponse a été générée.
Un autre cas d'utilisation pour un allocateur personnalisé (que j'ai utilisé) est l'écriture d'une unité de test pour prouver qu'une fonction du comportement ne dépend pas d'une certaine partie de son entrée. L'allocateur personnalisé peut remplir la mémoire de la région avec n'importe quel motif.
Lorsque l'on travaille avec des Gpu ou d'autres co-processeurs, il est quelquefois préférable d'allouer des structures de données dans la mémoire principale dans un manière spéciale. Cette manière spéciale de l'allocation de mémoire peuvent mises en œuvre dans un allocateur personnalisé dans une commode de la mode.
La raison pour laquelle l'allocation personnalisée grâce à l'accélérateur d'exécution peut être utile lors de l'utilisation des accélérateurs est la suivante:
Je suis en utilisant des allocateurs ici; on peut même dire que c'était de travailler autour de personnalisés gestion dynamique de la mémoire.
Contexte: nous avons surcharges pour malloc, calloc, gratuit, et les différentes variantes de l'opérateur new et delete, et l'éditeur de liens heureusement rend STL de les utiliser pour nous. Cela nous permet de faire des choses comme automatique petit pool d'objet, détection de fuite, alloc remplir, libre de remplissage, le remplissage de l'allocation avec les sentinelles, cache-ligne d'alignement de certaines allocations, et le retard de gratuit.
Le problème est que nous sommes en cours d'exécution dans un environnement embarqué -- il n'y a pas assez de mémoire autour de le faire réellement de détection de fuite de la comptabilité correctement sur une longue période. Au moins, pas dans la norme de la RAM, il y a un autre segment de mémoire RAM disponible ailleurs, par le biais de l'allocation personnalisée fonctions.
Solution: écrire un allocateur personnalisé qui utilise l'étendue de tas, et de l'utiliser seulement dans les entrailles de la fuite de mémoire suivi de l'architecture... Tout le reste par défaut à la normale de nouveau/supprimer des surcharges qui ne fuite de suivi. Cela évite le tracker, suivi lui-même (et donne un peu de son emballage supplémentaire de la fonctionnalité de trop, nous savons que la taille de tracker les nœuds).
Nous avons également l'utiliser pour garder la fonction de coût de profilage de données, pour la même raison; l'écriture d'une entrée pour chaque appel de fonction et de retour, ainsi que les commutateurs de thread, peut coûter cher rapide. Allocateur personnalisé nous permet de petites allocations dans un plus grand debug zone de mémoire.
Je suis à l'aide d'un allocateur personnalisé pour compter le nombre d'allocations/deallocations dans une partie de mon programme et de mesurer combien de temps cela prend. Il y a d'autres façons cela pourrait être réalisé, mais cette méthode est très pratique pour moi. Il est particulièrement utile que je peux utiliser l'allocateur personnalisé pour seulement un sous-ensemble de mes conteneurs.
L'un des éléments essentiels de la situation: Lors de l'écriture du code qui doit travailler à travers le module (EXE/DLL) limites, il est essentiel de garder vos allocations et les suppressions de passe dans un seul module.
Là que j'ai rencontré c'était une architecture de Plugin sur Windows. Il est essentiel que, par exemple, si vous passez un std::string à travers la DLL limite, que toute réaffectation de la chaîne se produisent dans le tas, d'où il provient, et NON le tas dans la DLL qui peuvent être différents*.
*C'est plus compliqué que cela en fait, comme si vous y sont liées dynamiquement à la CRT cela pourrait fonctionner de toute façon. Mais si chaque DLL a un lien statique pour le CRT vous êtes à la tête d'un monde de douleur, où le fantôme de l'allocation des erreurs se produisent continuellement.
Un exemple de j'ai le temps, j'ai utilisé ces a été de travailler avec des ressources très limitées des systèmes embarqués. Disons que vous avez 2k de ram libre et votre programme doit utiliser une partie de la mémoire. Vous avez besoin de stocker dire 4-5 séquences quelque part qui n'est pas sur la pile, et de plus, vous devrez avoir accès précis sur l'endroit où ces choses sont stockés, c'est une situation où vous pourriez écrire votre propre allocateur. Les implémentations par défaut peut fragment de la mémoire, ce qui pourrait être inacceptable si vous n'avez pas assez de mémoire et ne peut pas redémarrer votre programme.
Un projet sur lequel je travaillais a l'aide de AVR-GCC sur certaines puces de faible puissance. Nous avons eu de stocker 8 séquences de longueur variable, mais avec un maximum. Le la bibliothèque standard de mise en œuvre de la gestion de la mémoire est une fine enveloppe de malloc/free qui garde la trace de l'endroit où placer les éléments avec en les préfixant chaque bloc alloué de la mémoire avec un pointeur à peine passé la fin de ce qui est prévu morceau de mémoire. Lors de l'attribution d'un nouveau morceau de la mémoire de l'allocateur standard doit marcher sur chacun des morceaux de la mémoire pour trouver le bloc suivant qui est disponible lorsque la taille demandée de mémoire de forme. Sur une plateforme de bureau, ce serait très rapide de ces quelques éléments, mais vous devez garder à l'esprit que certains de ces microcontrôleurs sont très lents et primitive dans la comparaison. En outre, la fragmentation de la mémoire problème était un problème de masse qui veut dire que nous avons vraiment eu d'autre choix que de prendre une approche différente.
Ce que nous avons fait a été de mettre en œuvre notre propre pool de mémoire. Chaque bloc de mémoire était assez grand pour s'adapter à la plus grande séquence nous aurions besoin. Cette fixes réparties de la taille des blocs de mémoire à l'avance et qui a marqué des blocs de mémoire sont actuellement en cours d'utilisation. Nous l'avons fait en gardant un entier à 8 bits où chaque bit représenté, si un bloc a été utilisé. Nous avons échangé hors de l'utilisation de la mémoire ici pour tenter de rendre l'ensemble du processus plus rapide, ce qui dans notre cas a été justifiée comme nous avancions ce microcontrôleur de la puce proche de son maximum de capacité de traitement.
Il y a un certain nombre d'autres fois je peux voir de la rédaction de votre propre allocateur personnalisé dans le contexte des systèmes embarqués, par exemple, si la mémoire de la séquence n'est pas dans la ram principale comme on peut souvent le cas sur les ces plates-formes.
Obligatoire lien vers Andrei Alexandrescu est CppCon 2015 conférence sur allocateurs:
https://www.youtube.com/watch?v=LIb3L4vKZ7U
La bonne chose est que le point de mettre sur pied leur fait penser à des idées de comment vous pouvez l'utiliser 🙂
Pour la mémoire partagée, il est essentiel que non seulement le conteneur tête, mais aussi les données qu'il contient sont stockées dans la mémoire partagée.
L'allocateur de Boost::Interprocessus est un bon exemple. Cependant, comme vous pouvez le lire ici ce allone ne suffit pas, pour rendre tous les conteneurs STL mémoire partagée compatible (en Raison de différents cartographie des décalages dans les différents processus, les pointeurs peut "casser").
Il y a quelque temps, j'ai trouvé cette solution très utile pour moi: Rapide C++11 allocateur pour les conteneurs STL. Légèrement accélère les conteneurs STL sur VS2017 (~5x) ainsi que sur GCC (~7x). C'est un but spécial d'allocation basée sur la mémoire de la piscine. Il peut être utilisé avec des conteneurs STL seulement, grâce au mécanisme que vous demandez.
Personnellement, j'utilise Loki::Allocator /SmallObject optimiser l'utilisation de mémoire pour les petits objets — il, montrent une bonne efficacité et des performances satisfaisantes si vous avez à travailler avec des quantités modérées de très petits objets (de 1 à 256 octets). Il peut être jusqu'à ~30 fois plus efficace que la norme C++ new/delete allocation si nous parlons de l'allocation de quantités modérées de petits objets de différentes tailles. Aussi, il y a un VC-solution spécifique appelé "QuickHeap", il apporte les meilleures performances possibles (allouer et de libérer les opérations de lecture et d'écriture de l'adresse du bloc alloué/retournés à tas, respectivement jusqu'à 99.(9)% des cas, dépend de paramètres et d'initialisation), mais à un coût d'un notable de la tête — il a besoin de deux pointeurs par mesure et une personne supplémentaire pour chaque nouveau bloc de mémoire. C'est plus rapide possible solution pour travailler avec de grands (10 000++) montants des objets créés et supprimés si vous n'avez pas besoin d'une grande variété d'objets de tailles (il crée une piscine individuelle pour chaque objet de la taille, de 1 à 1023 octets en œuvre, de sorte que l'initialisation des coûts peut rabaisser la performance globale boost, mais on peut aller de l'avant et d'allouer/désallouer mannequin objets avant l'application entre les performances de la phase critique(s)).
De la question avec le standard C++ new/delete mise en œuvre est qu'il est généralement juste un wrapper pour C malloc/free allocation, et il fonctionne bien, pour les plus grands blocs de la mémoire, comme 1024+ octets. Il a une remarquable généraux en termes de performance et, parfois, plus de mémoire utilisée pour la cartographie de trop. Ainsi, dans la plupart des cas, la coutume allocateurs sont mis en œuvre de manière à maximiser la performance et/ou de minimiser la quantité de mémoire nécessaire pour l'allocation de petite taille (≤à 1 024 octets) des objets.
Dans un graphique de simulation, j'ai vu personnalisé allocateurs utilisé pour
std::allocator
n'a pas directement en charge.