Stabilité de la mémoire d’une application C ++ sous Linux

Je veux vérifier la stabilité de la mémoire d’une application C ++ que j’ai écrite et compilée pour Linux. C’est une application réseau qui répond aux connexions des clients distants à un taux de 10 à 20 connexions par seconde. À long terme, la mémoire passait à 50 Mo, bien que l’appli ait effectué des appels à la suppression …

L’enquête montre que Linux ne libère pas immédiatement la mémoire. Donc, voici mes questions :

Comment forcer Linux à libérer de la mémoire? Au moins, je veux le faire une fois pour vérifier la stabilité de la mémoire. Sinon, existe-t-il un indicateur de mémoire fiable capable de signaler la mémoire de mon application?

Ce que vous voyez n’est probablement pas une fuite de mémoire du tout. Les systèmes d’exploitation et malloc / new hea utilisent tous deux une comptabilité très complexe de nos jours. C’est en général une très bonne chose. Toute tentative de votre part pour forcer le système d’exploitation à libérer de la mémoire ne nuira qu’aux performances de votre application et aux performances globales du système.

Pour illustrer:

  1. Le tas réserve plusieurs zones de mémoire virtuelle à utiliser. Aucun de ceux-ci n’est réellement engagé (sauvegardé par la mémoire physique) tant que malloc’d.

  2. Vous allouez de la mémoire. Le tas grandit en conséquence. Vous voyez ceci dans le gestionnaire de tâches.

  3. Vous allouez plus de mémoire sur le tas. Il pousse plus.

  4. Vous libérez de la mémoire allouée à l’étape 2. Le tas ne peut pas rétrécir, cependant, car la mémoire dans # 3 est toujours allouée et Heaps ne peut pas compacter la mémoire (cela invaliderait vos pointeurs).

  5. Vous malloc / new more stuff. Cela peut être corrigé après la mémoire allouée à l’étape n ° 3, car il ne peut pas tenir dans la zone laissée ouverte par la libération n ° 2, ou parce qu’il serait inefficace pour le gestionnaire de tas de parcourir le bloc laissé ouvert par # 2. (dépend de l’implémentation du tas et de la taille de la mémoire allouée / libérée)

Alors, cette mémoire à l’étape 2 est-elle maintenant morte pour le monde? Pas nécessairement. D’une part, il sera probablement réutilisé éventuellement, une fois qu’il sera efficace. Dans les cas où il n’est pas réutilisé, le système d’exploitation lui-même peut utiliser les fonctionnalités de mémoire virtuelle du processeur (TLB) pour «remapper» la mémoire inutilisée directement sous votre application et l’assigner à une autre application – sur la mouche. Le tas le sait et gère généralement les choses de manière à améliorer la capacité du système d’exploitation à remapper les pages.

Ce sont des techniques précieuses de gestion de la mémoire qui ont pour effet secondaire non négligeable de rendre la détection de fuite de mémoire fine via Process Explorer la plupart du temps inutile. Si vous souhaitez détecter les petites memory leaks dans le tas, vous devrez utiliser les outils de détection des fuites de tas à l’exécution. Étant donné que vous avez mentionné que vous êtes également capable de construire sous Windows, je noterai que le CRT de Microsoft dispose d’outils de vérification des fuites intégrés. Instructions d’utilisation trouvées ici:

http://msdn.microsoft.com/en-us/library/974tc9t1(v=vs.100).aspx

Il y a aussi des remplacements open-source pour malloc disponibles pour une utilisation avec les chaînes d’outils GCC / Clang, même si je n’ai aucune expérience directe avec eux. Je pense que sous Linux, Valgrind est la méthode préférée et la plus fiable pour détecter les fuites. (et dans mon expérience plus facile à utiliser que MSVCRT Debug).

Je suggère d’utiliser valgrind avec l’outil memcheck ou tout autre outil de profilage pour les memory leaks

de la page de Valgrind:

Memcheck

détecte les problèmes de gestion de la mémoire et vise principalement les programmes C et C ++. Lorsqu’un programme est exécuté sous la supervision de Memcheck, toutes les lectures et écritures de mémoire sont vérifiées et les appels à malloc / new / free / delete sont interceptés. Memcheck peut ainsi détecter si votre programme:

  • Accède à la mémoire qu’il ne doit pas (zones non encore allouées, zones libérées, zones situées à la fin des blocs de tas, zones inaccessibles de la stack).
  • Utilise des valeurs non initialisées de manière dangereuse.
  • Fuite de mémoire.
  • Est-ce que bad frees les blocs de tas (libère le double, libère le libèrent).
  • Transmet les blocs de mémoire source et de destination qui se chevauchent à memcpy () et aux fonctions associées.

Memcheck signale ces erreurs dès qu’elles se produisent, en indiquant le numéro de la ligne source à laquelle elles se sont produites, ainsi qu’une trace de stack des fonctions appelées pour atteindre cette ligne. Memcheck suit l’adressabilité au niveau de l’octet et l’initialisation des valeurs au niveau du bit. En conséquence, il peut détecter l’utilisation de bits non initialisés et ne signale pas d’erreurs parasites sur les opérations sur les champs de bits. Memcheck exécute des programmes d’environ 10-30 fois plus lentement que la normale. Cachegrind

Massif

Massif est un profileur de tas. Il effectue un profilage de tas détaillé en prenant des instantanés réguliers du tas du programme. Il produit un graphique montrant l’utilisation du tas au fil du temps, y compris des informations sur les parties du programme qui sont responsables de la plupart des allocations de mémoire. Le graphique est complété par un fichier texte ou HTML contenant davantage d’informations pour déterminer où la plus grande quantité de mémoire est allouée. Massif exécute des programmes environ 20 fois plus lents que la normale.

Utiliser valgrind est aussi simple que d’exécuter une application avec les commutateurs souhaités et lui donner comme entrée de valgrind:

valgrind --tool=memcheck ./myapplication -f foo -b bar 

Je doute fort que quoi que ce soit au-delà de l’emballage de malloc et free [ou new et delete ] avec une autre fonction, vous obtiendrez autre chose que des estimations très approximatives.

L’un des problèmes est que la mémoire libérée ne peut être libérée que s’il ya un long morceau de mémoire contigu. Ce qui se passe généralement, c’est qu’il ya des “petits morceaux” de mémoire qui sont utilisés partout dans le tas, et vous ne pouvez pas trouver un gros morceau qui peut être libéré.

Il est hautement improbable que vous puissiez résoudre ce problème de manière simple.

Et à propos, votre application va probablement avoir besoin de ces 50 Mo plus tard lorsque vous aurez encore plus de charge.

(Si la mémoire que vous n’utilisez pas est nécessaire pour autre chose, elle sera remplacée et les pages qui ne seront pas touchées pendant une longue période seront les premiers candidats, donc si le système manque de mémoire pour d’autres tâches, va toujours réutiliser la mémoire vive de votre machine pour cet espace, donc il ne rest pas là – il est juste que vous ne pouvez pas utiliser «ps» ou certains autres pour déterminer combien votre programme utilise RAM!)

Comme suggéré dans un commentaire: Vous pouvez également écrire votre propre allocateur de mémoire, en utilisant mmap() pour créer un “morceau” pour dissortingbuer des parties. Si vous avez une section de code qui fait beaucoup d’allocations de mémoire, et que TOUTES celles-ci seront définitivement libérées plus tard, pour allouer toutes celles d’un morceau de mémoire séparé, et quand tout sera libéré, vous pouvez mettre le mmap ‘ d région retourne dans une “liste mmap gratuite”, et lorsque la liste est suffisamment volumineuse, libérez certaines des allocations mmap [ceci afin d’éviter d’appeler mmap LOTS de fois, puis de munmap quelques millisconds plus tard]. Cependant, si vous laissez JAMAIS une de ces allocations de mémoire “s’échapper” de votre zone clôturée, votre application va probablement tomber en panne (ou pire, ne pas planter, mais utiliser de la mémoire appartenant à une autre partie de l’application et résultat étrange quelque part, tel qu’un utilisateur voit le contenu du réseau supposé être pour un autre utilisateur!)

Utilisez valgrind pour rechercher les memory leaks: valgrind ./votre_application

Il listera où vous avez alloué de la mémoire et ne l’a pas libéré.

Je ne pense pas que ce soit un problème Linux, mais dans votre application. Si vous surveillez l’utilisation de la mémoire avec «top», vous n’obtiendrez pas d’utilisations très précises. Essayez d’utiliser massif (un outil de valgrind): valgrind –tool = massif ./votre_application pour connaître la véritable utilisation de la mémoire.

En règle générale, pour éviter les fuites en C ++: utilisez des pointeurs intelligents au lieu de pointeurs normaux. Dans de nombreuses situations, vous pouvez également utiliser RAII (http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) au lieu d’allouer de la mémoire à «new».

Il n’est pas habituel pour un système d’exploitation de libérer de la mémoire lorsque vous appelez free ou delete . Cette mémoire revient au gestionnaire de segments dans la bibliothèque d’exécution.

Si vous voulez réellement libérer de la mémoire, vous pouvez utiliser brk . Mais cela ouvre une très grande quantité de vers de gestion de la mémoire. Si vous appelez directement brk , mieux vaut ne pas appeler malloc . Pour C ++, vous pouvez remplacer les new pour utiliser brk directement.

Pas une tâche facile.

Le dernier dlmalloc () a un concept appelé mspace (d’autres l’appellent une région). Vous pouvez appeler malloc () et free () contre un mspace. Vous pouvez également supprimer le mspace pour libérer toute la mémoire allouée depuis le mspace. La suppression d’un espace libère de la mémoire du processus.

Si vous créez un mspace avec une connexion, allouez toute la mémoire pour la connexion à partir de cet espace et supprimez le mspace lorsque la connexion se ferme, vous n’auriez aucune croissance de processus.

Si vous avez un pointeur dans un espace menant à la mémoire dans un autre espace, et que vous supprimez le deuxième espace, alors, comme le disent les juristes, “les résultats ne sont pas définis”.