Exploration du moteur VoIP Mediastreamer2. Partie 12

Le matériel de l'article est tiré de mon canal zen.

Exploration du moteur VoIP Mediastreamer2. Partie 12

À la fin article, j'ai promis d'examiner la question de l'estimation de la charge du ticker et des moyens de gérer une charge de calcul excessive dans le streamer multimédia. Mais j'ai décidé qu'il serait plus logique de couvrir les problèmes de débogage des filtres artisanaux liés au mouvement des données et de ne considérer qu'ensuite les problèmes d'optimisation des performances.

Débogage des filtres d'artisanat

Après avoir examiné le mécanisme de déplacement des données dans un streamer multimédia dans l'article précédent, il serait logique de parler des dangers qui y sont cachés. L'une des particularités du principe du "flux de données" est que l'allocation de mémoire à partir du tas se fait dans les filtres situés aux sources du flux de données, et les filtres situés à la fin du chemin du flux désallouent déjà la mémoire avec retour au tas. De plus, la création de nouvelles données et leur destruction peuvent se produire quelque part dans les points intermédiaires. En général, la libération de mémoire est effectuée par un filtre autre que celui qui a créé le bloc de données.

Du point de vue de la surveillance transparente de la mémoire, il serait raisonnable que le filtre, lors de la réception d'un bloc d'entrée, le détruise immédiatement après le traitement, libère de la mémoire et place un bloc nouvellement créé avec des données de sortie sur la sortie. Dans ce cas, la fuite de mémoire dans le filtre pourrait être facilement tracée - si l'analyseur détecte une fuite dans le filtre, le filtre qui le suit ne détruit pas correctement les blocs entrants et contient une erreur. Mais du point de vue du maintien de hautes performances, cette approche du travail avec des blocs de données n'est pas productive - elle conduit à un grand nombre d'opérations pour allouer/libérer de la mémoire pour les blocs de données sans aucun épuisement utile.

Pour cette raison, les filtres media streamer, afin de ne pas ralentir le traitement des données, utilisent des fonctions qui créent des copies légères lors de la copie des messages (nous en avons parlé dans un article précédent). Ces fonctions créent uniquement une nouvelle copie de l'en-tête de message en "joignant" le bloc de données de l'"ancien" message copié à celui-ci. En conséquence, deux en-têtes sont attachés à un bloc de données et le compteur de référence dans le bloc de données est incrémenté. Mais cela ressemblera à deux messages. Il peut y avoir plus de messages avec un tel bloc de données "public", par exemple, le filtre MS_TEE génère dix copies lumineuses de ce type à la fois, les répartissant entre ses sorties. Si tous les filtres de la chaîne fonctionnent correctement, à la fin du pipeline, ce nombre de références devrait atteindre zéro et la fonction de désallocation de mémoire sera appelée : ms_free(). Si l'appel ne se produit pas, alors ce morceau de mémoire ne sera plus renvoyé au tas, c'est-à-dire il "fuit". Le coût d'utilisation des copies légères est la perte de la capacité de déterminer facilement (comme ce serait le cas dans le cas de l'utilisation de copies régulières) dans quel filtre graphique la mémoire fuit.

Étant donné que la responsabilité de trouver des fuites de mémoire dans les filtres "natifs" incombe aux développeurs du diffuseur multimédia, vous n'aurez probablement pas à les déboguer. Mais avec votre filtre d'artisanat, vous êtes vous-même la sauterelle de votre propre bonheur, et le temps que vous passerez à chercher des fuites dans votre code dépendra de votre précision. Pour réduire votre temps de débogage, nous devons examiner les techniques de localisation des fuites lors de la conception des filtres. De plus, il peut arriver que la fuite ne se manifeste que lors de l'application du filtre dans un système réel, où le nombre de "suspects" peut être énorme et le temps de débogage limité.

Comment se manifeste une fuite mémoire ?

Il est logique de supposer que dans la sortie du programme top affichera un pourcentage croissant de mémoire occupée par votre application.

La manifestation externe consistera dans le fait qu'à un moment donné, le système réagira lentement au mouvement de la souris, redessinant lentement l'écran. Il est également possible que le journal système grandisse, consommant de l'espace sur le disque dur. Dans ce cas, votre application commencera à se comporter de manière étrange, ne répondant pas aux commandes, ne pourra pas ouvrir le fichier, etc.

Pour identifier le fait d'une fuite, on utilisera un analyseur de mémoire (ci-après dénommé l'analyseur). Il pourrait être Valgrind (bien article à ce sujet) ou intégré au compilateur gcc Désinfectant pour la mémoire ou autre chose. Si l'analyseur montre que la fuite se produit dans l'un des filtres graphiques, cela signifie qu'il est temps d'appliquer l'une des méthodes décrites ci-dessous.

Méthode des Trois Pins

Comme mentionné ci-dessus, en cas de fuite de mémoire, l'analyseur pointera vers le filtre qui a demandé l'allocation de mémoire à partir du tas. Mais il ne pointera pas vers le filtre qui "a oublié" de le renvoyer, ce qui, en fait, est à blâmer. Ainsi, l'analyseur ne peut que confirmer nos peurs, mais pas indiquer leur racine.

Pour connaître l'emplacement du "mauvais" filtre dans le graphique, vous pouvez aller en réduisant le graphique au nombre minimum de nœuds au niveau desquels l'analyseur détecte toujours une fuite et localiser le filtre problématique dans les trois pins restants.

Mais il peut arriver qu'en réduisant le nombre de filtres dans la colonne vous perturbiez le cours normal de l'interaction entre les filtres et les autres éléments de votre système et la fuite n'apparaîtra plus. Dans ce cas, vous devrez travailler avec un graphique en taille réelle et utiliser l'approche décrite ci-dessous.

Méthode de l'isolateur coulissant

Pour simplifier la présentation, nous utiliserons un graphe constitué d'une seule chaîne de filtres. Elle est montrée sur la photo.

Exploration du moteur VoIP Mediastreamer2. Partie 12

Un graphique ordinaire, dans lequel, avec des filtres de diffusion multimédia prêts à l'emploi, quatre filtres artisanaux F1…F4 sont utilisés, quatre types différents que vous avez créés il y a longtemps et dont vous n'avez aucun doute sur l'exactitude. Cependant, supposons que plusieurs d'entre eux aient une fuite de mémoire. Lors de l'exécution de notre programme de supervision d'analyseur, nous apprenons de son rapport qu'un certain filtre a demandé une certaine quantité de mémoire et ne l'a pas renvoyée au tas N fois. Il est facile de deviner qu'il y aura une référence aux fonctions de filtrage interne du type MS_VOID_SOURCE. Sa tâche est de prendre la mémoire du tas. D'autres filtres devraient y retourner. Ceux. nous trouverons la fuite.

Afin de déterminer à quelle section du pipeline s'est produite une inactivité qui a conduit à une fuite de mémoire, il est proposé d'introduire un filtre supplémentaire qui déplace simplement les messages de l'entrée vers la sortie, mais crée en même temps une copie non lumineuse de l'entrée message dans une copie "lourde" normale, puis supprimant complètement le message qui est arrivé à l'entrée. Nous appellerons un tel filtre un isolant. Nous pensons que puisque le filtre est simple, les fuites sont exclues. Et une autre propriété positive - si nous l'ajoutons à n'importe quel endroit de notre graphique, cela n'affectera en rien le fonctionnement du circuit. Nous allons représenter le filtre isolant comme un cercle à double contour.

Activez l'isolateur juste après le filtre voidsourse :
Exploration du moteur VoIP Mediastreamer2. Partie 12

Nous exécutons à nouveau le programme avec l'analyseur, et nous voyons que cette fois, l'analyseur rejettera la faute sur l'isolateur. Après tout, c'est lui qui crée maintenant des blocs de données, qui sont ensuite perdus par un filtre (ou des filtres) négligent inconnu. L'étape suivante consiste à déplacer l'isolant le long de la chaîne vers la droite, d'un filtre, et à recommencer l'analyse. Ainsi, pas à pas, en déplaçant l'isolateur vers la droite, nous obtenons une situation où le nombre de blocs de mémoire "fuis" dans le prochain rapport d'analyseur diminue. Cela signifie qu'à cette étape, l'isolant s'est retrouvé dans la chaîne immédiatement après le filtre problématique. S'il n'y avait qu'un seul "mauvais" filtre, la fuite disparaîtra complètement. Ainsi, nous avons localisé le filtre problématique (ou l'un de plusieurs). Après avoir "réparé" le filtre, nous pouvons continuer à déplacer l'isolateur vers la droite le long de la chaîne jusqu'à ce que les fuites de mémoire soient complètement éliminées.

Implémentation d'un filtre isolateur

L'implémentation de l'isolateur ressemble à un filtre normal. En tête de fichier:

/* Файл 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 lui-même :

/* Файл 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 remplacement des fonctions de gestion de la mémoire

Pour des recherches plus subtiles, le streamer multimédia offre la possibilité de remplacer les fonctions d'accès à la mémoire par les vôtres, qui, en plus du travail principal, enregistreront "Qui, où et pourquoi". Trois fonctions sont remplacées. Cela se fait de la manière suivante:

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 fonctionnalité vient à la rescousse dans les cas où l'analyseur ralentit tellement les filtres que le fonctionnement du système dans lequel notre circuit est construit est perturbé. Dans une telle situation, vous devez abandonner l'analyseur et utiliser la substitution des fonctions de mémoire.

Nous avons considéré un algorithme d'actions pour un graphe simple qui ne contient pas de branches. Mais cette approche peut être appliquée à d'autres cas, bien sûr avec complication, mais l'idée reste la même.

Dans le prochain article, nous examinerons la question de l'estimation de la charge du ticker et comment gérer une charge de calcul excessive dans le streamer multimédia.

Source: habr.com

Ajouter un commentaire