Le contenu de cet article est tiré de ma .

Dans le passé, , j'ai promis d'examiner la question de l'évaluation de la charge sur le ticker et les moyens de lutter contre une charge de calcul excessive dans le mediastreamer. Mais j'ai pensé qu'il serait plus logique d'aborder les questions de débogage des filtres artisanaux, liées au déplacement des données, puis d'examiner les questions d'optimisation des performances.
Débogage des filtres artisanaux
Après avoir examiné dans l'article précédent le mécanisme de déplacement des données dans le mediastreamer, il serait logique de discuter des dangers qui s'y cachent. L'une des particularités du principe de "data flow" est que l'allocation de mémoire depuis le tas se produit dans les filtres situés aux sources du flux de données, tandis que la libération de mémoire avec retour au tas est effectuée par les filtres situés à la fin du parcours du flux. De plus, la création de nouvelles données et leur destruction peuvent se produire à des points intermédiaires. En général, c'est le filtre qui n'a pas créé le bloc de données qui effectue la libération de mémoire.
Du point de vue d'une surveillance transparente de la mémoire, il serait raisonnable que le filtre, en recevant un bloc d'entrée, le détruise immédiatement après traitement avec libération de mémoire et présente à la sortie un nouveau bloc de données de sortie. Dans ce cas, une fuite de mémoire dans le filtre serait facilement traçable : si l'analyseur détecte une fuite dans le filtre, cela signifie que le filtre suivant ne détruit pas correctement les blocs entrants, et qu'il y a une erreur dans celui-ci. Mais du point de vue du maintien d'une haute performance, cette approche de traitement des blocs de données n'est pas productive — elle entraîne un grand nombre d'opérations d'allocation/libération de mémoire pour les blocs de données sans aucun résultat utile.
Pour cette raison, les filtres de media streamer, afin de ne pas ralentir le traitement des données, utilisent lors de la copie des messages des fonctions qui créent des copies légères (nous en avons parlé dans notre dernier article). Ces fonctions créent uniquement une nouvelle instance de l'en-tête du message "en attachant" à celui-ci un bloc de données du "vieux" message copié. En conséquence, un même bloc de données se trouve lié à deux en-têtes et le compteur de références dans le bloc de données est incrémenté. Cependant, cela apparaîtra comme deux messages. Il peut y avoir plusieurs messages avec un bloc de données "partagé" ; par exemple, le filtre MS_TEE génère immédiatement une dizaine de ces copies légères, les distribuant sur ses sorties. Lorsque tous les filtres de la chaîne fonctionnent correctement, à la fin de la chaîne de traitement, ce compteur de références devrait atteindre zéro et la fonction de libération de mémoire sera appelée : ms_free(). Si l'appel ne se produit pas, cela signifie que ce morceau de mémoire ne reviendra pas au tas, c'est-à-dire qu'il "fuit". Le prix à payer pour l'utilisation de copies légères est la perte de la possibilité de déterminer facilement (comme cela aurait été le cas avec des copies normales) quel filtre provoque la fuite de mémoire.
Étant donné que la responsabilité de la détection des fuites de mémoire dans les filtres "natifs" incombe aux développeurs du media streamer, il est donc probable que vous n'aurez pas à les déboguer. Mais pour votre filtre artisanal — vous êtes le maître de votre bonheur et votre attention déterminera le temps que vous passerez à chercher des fuites dans votre code. Afin de réduire votre temps de souffrance lors du débogage, nous devons examiner les techniques de localisation des fuites lors du développement des filtres. De plus, il se peut qu'une fuite ne se manifeste que lorsqu'on applique le filtre dans un système réel, où le nombre de "soupçons" peut être énorme et le temps de débogage limité.
Comment se manifeste une fuite de mémoire ?
Il est logique de supposer que dans la sortie du programme top un pourcentage croissant de mémoire utilisé par votre application sera affiché.
L'apparition extérieure se traduira par un ralentissement de la réponse du système aux mouvements de la souris et une lenteur dans le rafraîchissement de l'écran. Il se peut également que le journal système augmente, occupant de l'espace sur le disque dur. Par conséquent, votre application commencera à se comporter de manière étrange, ne réagira pas aux commandes, ne pourra pas ouvrir des fichiers, etc.
Pour identifier le fait qu'une fuite de mémoire s'est produite, nous allons utiliser un analyseur de mémoire (ci-après dénommé analyseur). Cela peut être Valgrind (un bon à son sujet) ou intégré dans le compilateur gcc ou autre chose. Si l'analyseur indique qu'une fuite se produit dans l'un des filtres du graphe, cela signifie qu'il est temps d'appliquer l'une des méthodes décrites ci-dessous.
Méthode des trois filtres
Comme mentionné précédemment, en cas de fuite de mémoire, l'analyseur pointera vers le filtre qui a demandé l'allocation de mémoire dans le tas. Mais il ne pointera pas vers le filtre qui a "oublié" de la libérer, qui est en réalité le coupable. Ainsi, l'analyseur peut seulement confirmer nos soupçons, mais ne peut pas en identifier la source.
Pour déterminer l'emplacement du filtre "défavorable" dans le graphe, vous pouvez réduire le graphe au nombre minimal de nœuds pour lequel l'analyseur détecte encore la fuite et localiser le filtre problématique parmi les trois derniers nœuds.
Mais il peut arriver qu'en réduisant le nombre de filtres dans le graphe, vous perturbiez le fonctionnement habituel des filtres avec d'autres éléments de votre système et que la fuite cesse de se manifester. Dans ce cas, vous devrez travailler avec le graphe complet et utiliser l'approche décrite ci-dessous.
Méthode de l'isolateur glissant
Pour simplifier l'exposé, utilisons un graphe qui consiste en une chaîne de filtres. Il est illustré sur l'image.
![]()
Un graph ordinaire, où sont appliqués les filtres prêts du mediastreamer ainsi que quatre filtres artisanaux F1…F4, de quatre types différents, que vous avez créés il y a longtemps et dont vous ne doutez pas de la validité. Cependant, supposons qu'il y ait une fuite de mémoire dans certains d'entre eux. En exécutant notre programme de surveillance du vérificateur, le rapport nous informera qu'un certain filtre a demandé une certaine quantité de mémoire et ne l'a pas restituée à la mémoire un certain nombre de fois. Il est facile de deviner qu'il y aura un lien avec les fonctions internes du filtre de type MS_VOID_SOURCE. Sa tâche est de récupérer de la mémoire dans le tas. D'autres filtres doivent la lui rendre. Autrement dit, nous découvrirons la réalité de la fuite.
Pour déterminer à quel endroit de la chaîne de production l'inaction ayant conduit à la fuite de mémoire s'est produite, il est proposé d'introduire un filtre supplémentaire, qui ne fait que transférer les messages de l'entrée à la sortie, tout en créant une copie normale "lourde" de l'entrée, puis en supprimant complètement le message qui est arrivé à l'entrée. Appelons ce filtre un isolateur. Nous supposons que, puisque le filtre est simple, la fuite en lui est exclue. Encore une propriété positive : si nous l'ajoutons en n'importe quel endroit de notre graph, cela n'affectera pas le fonctionnement du schéma. Nous représenterons le filtre-isolateur sous la forme d'un cercle à double contour.
Nous activons l'isolateur juste après le filtre voidsource :
![]()
Nous relançons à nouveau le programme avec le vérificateur, et voyons que cette fois, le vérificateur imputera la responsabilité à l'isolateur. Car c'est lui qui crée maintenant des blocs de données, qui sont ensuite perdus par un filtre négligent (ou des filtres). La prochaine étape consiste à déplacer l'isolateur d'un filtre en arrière dans la chaîne et à relancer l'analyse. Ainsi, en déplaçant pas à pas l'isolateur à droite, nous obtiendrons une situation où dans le prochain rapport du vérificateur, le nombre de blocs de mémoire "fuités" diminuera. Cela signifie qu'à ce stade, l'isolateur se trouvait dans la chaîne juste après le filtre problématique. Si le "mauvais" filtre n'était qu'un, alors la fuite disparaîtra complètement. Ainsi, nous avons localisé le filtre problématique (ou l'un des plusieurs). En "réparant" le filtre, nous pouvons continuer à déplacer l'isolateur à droite dans la chaîne jusqu'à ce que les fuites de mémoire soient complètement maîtrisées.
Mise en œuvre du filtre-isolateur
La mise en œuvre de l'isolateur ressemble à celle d'un filtre ordinaire. Fichier d'en-tête :
/* Файл iso_filter.h Описание изолирующего фильтра. */
#ifndef iso_filter_h
#define iso_filter_h
/* Задаем идентификатор фильтра. */
#include <mediastreamer2/msfilter.h>
#define MY_ISO_FILTER_ID 1024
extern MSFilterDesc iso_filter_desc;
#endif
Le filtre :
/* Файл iso_filter.c Описание изолирующего фильтра. */
#include "iso_filter.h"
static void
iso_init (MSFilter * f)
{
}
static void
iso_uninit (MSFilter * f)
{
}
static void
iso_process (MSFilter * f)
{
mblk_t *im;
while ((im = ms_queue_get (f->inputs[0])) != NULL)
{
ms_queue_put (f->outputs[0], copymsg (im));
freemsg (im);
}
}
static MSFilterMethod iso_methods[] = {
{0, NULL}
};
MSFilterDesc iso_filter_desc = {
MY_ISO_FILTER_ID,
"iso_filter",
"A filter that reads from input and copy to its output.",
MS_FILTER_OTHER,
NULL,
1,
1,
iso_init,
NULL,
iso_process,
NULL,
iso_uninit,
iso_methods
};
MS_FILTER_DESC_EXPORT (iso_desc)
Méthode de substitution des fonctions de gestion de la mémoire
Pour des recherches plus détaillées, le mediastreamer permet de remplacer les fonctions d'accès à la mémoire par les vôtres, qui, en plus de leur fonctionnalité principale, enregistreront "Qui, où et pourquoi". Trois fonctions sont remplacées. Cela se fait comme suit :
OrtpMemoryFunctions reserv;
OrtpMemoryFunctions my;
reserv.malloc_fun = ortp_malloc;
reserv.realloc_fun = ortp_realloc;
reserv.free_fun = ortp_free;
my.malloc_fun = &my_malloc;
my.realloc_fun = &my_realloc;
my.free_fun = &my_free;
ortp_set_memory_functions(&my);Cette possibilité est utile lorsque l'analyseur ralentit les filtres au point de perturber le fonctionnement du système dans lequel notre schéma est intégré. Dans cette situation, il faut renoncer à l'analyseur et utiliser la substitution des fonctions de gestion de la mémoire.
Nous avons examiné l'algorithme d'action pour un graphique simple, sans branches. Mais cette approche peut également être appliquée à d'autres cas, bien sûr avec une complexité accrue, mais l'idée restera la même.
Dans le prochain article, nous traiterons de l'évaluation de la charge sur le tickeur et des moyens de lutter contre une charge de calcul excessive dans le mediastreamer.
Source : habr.com
