Materialul articolului provine de pe canalul meu .

În trecut, , am promis să analizez problemele legate de evaluarea sarcinii pe ticker și modalitățile de a combate încărcarea computațională excesivă în mediastreamer. Totuși, am decis că ar fi mai logic să discut despre problemele de depanare a filtrelor personalizate, legate de transferul de date și abia apoi să abordez aspectele optimizării performanței.
Depanarea filtrelor personalizate
După ce în articolul anterior am discutat despre mecanismul de transfer al datelor în mediastreamer, ar fi logic să vorbim despre pericolele care se ascund în acesta. Una dintre caracteristicile principiului "data flow" constă în faptul că alocarea memoriei din heap se face în filtrele care se află la începutul fluxului de date, iar eliberarea memoriei cu returnarea în heap se face deja de filtrele situate la sfârșitul căii fluxului. În plus, crearea de date noi și distrugerea acestora poate avea loc undeva în puncte intermediare. În general, eliberarea memoriei este realizată nu de filtrul care a creat blocul de date.
Din perspectiva monitorizării transparente a memoriei, ar fi rațional ca filtrul, la primirea blocului de intrare, să-l distrugă imediat după procesare, eliberând astfel memoria, iar la ieșire să prezinte un bloc nou creat cu datele de ieșire. În acest caz, scurgerea de memorie în filtru ar putea fi ușor urmărită — dacă analizatorul detectează o scurgere în filtru, atunci înseamnă că filtrul următor nu distruge blocurile de intrare în mod corespunzător și această eroare îi aparține. Dar din perspectiva menținerii unei performanțe ridicate, această abordare a lucrului cu blocurile de date nu este productivă — duce la un număr mare de operațiuni de alocare/eliberare a memoriei pentru blocurile de date fără un rezultat util.
Din acest motiv, filtrele mediarului de streaming, pentru a nu încetini procesarea datelor, folosesc la copierea mesajelor funcții care creează copii ușoare (despre acestea am discutat în articolul anterior). Aceste funcții creează doar o nouă instanță a antetului mesajului "prinzând" la acesta un bloc de date din "vechiul" mesaj copiat. Ca urmare, două antete se leagă de un singur bloc de date, și contorul de referințe din blocul de date este incrementat. Totuși, acesta va apărea ca două mesaje. Mesaje cu un astfel de bloc de date "împărtășit" pot fi chiar mai multe; de exemplu, filtrul MS_TEE generează imediat o duzină de astfel de copii ușoare, distribuite pe ieșirile sale. Dacă toate filtrele din lanț funcționează corect, la sfârșitul procesului acest contor de referințe ar trebui să ajungă la zero și va fi apelată funcția de eliberare a memoriei: ms_free(). Dacă apelul nu are loc, atunci acest segment de memorie nu se va întoarce în heap, adică va "preterea". Prețul pentru utilizarea copiilor ușoare este pierderea posibilității de a stabili ușor (așa cum s-ar fi întâmplat în cazul folosirii copiilor obișnuite) în care filtru grafic se pierde memoria.
Deoarece responsabilitatea pentru găsirea scurgerilor de memorie în filtrele "native" revine dezvoltatorilor mediarului de streaming, probabil nu va trebui să le debugați. Însă, cu filtrul vostru personalizat — voi sunteți cuțitul fericirii voastre, iar de atenția voastră va depinde timpul pe care îl veți petrece căutând scurgeri în codul vostru. Pentru a vă scurta timpul de debugging, ar trebui să examinăm tehnicile de localizare a scurgerilor în timpul dezvoltării filtrelor. De asemenea, poate apărea ca o scurgere să se manifeste doar atunci când filtrul este utilizat într-un sistem real, unde numărul de "suspecți" poate fi imens, iar timpul pentru debugging este limitat.
Cum se manifestă o scurgere de memorie?
E logic să presupunem că în ieșirea programului top va fi arătat un procent crescător de memorie ocupat de aplicația voastră.
Manifestarea externă va consta în momentul în care sistemul va începe să reacționeze lent la mișcarea mouse-ului, să redimensioneze încet ecranul. De asemenea, este posibil ca jurnalul de sistem să crească, ocupând spațiu pe hard disk. În acest moment, aplicația dumneavoastră va începe să se comporte ciudat, să nu răspundă la comenzi, să nu poată deschide fișiere etc.
Pentru a identifica existența unei scurgeri, vom folosi un analizator de memorie (mai departe denumit analizator). Acesta poate fi Valgrind (bun despre el) sau integrat în compilator gcc sau altceva. Dacă analizatorul arată că scurgerea are loc într-unul dintre filtrele grafului, atunci aceasta înseamnă că este timpul să aplicăm una dintre metodele descrise mai jos.
Metoda celor trei pini
După cum am menționat anterior, în cazul unei scurgeri de memorie, analizatorul va indica filtrul care a solicitat alocarea de memorie din heap. Dar nu va indica filtrul care a "uitat" să o returneze, care este, de fapt, vinovat. Astfel, analizatorul poate doar să confirme temerile noastre, dar nu poate indica rădăcina acestora.
Pentru a determina locația filtrelor "necorespunzătoare" în graf, se poate merge pe calea reducerii grafului la un număr minim de noduri, în care analizatorul să detecteze încă scurgerea și în cele trei pini rămase să localizăm filtrul problematic.
Dar poate apărea situația în care, reducând numărul filtrelor în graf, veți perturba fluxul obișnuit de interacțiune al filtrelor cu celelalte elemente ale sistemului dumneavoastră și scurgerea va înceta să se manifeste. În acest caz, va trebui să lucrați cu graful complet și să folosiți abordarea care este descrisă mai jos.
Metoda izolatului mobil
Pentru simplitatea expunerii, ne vom folosi de un graf care constă dintr-un singur lanț de filtre. Acesta este ilustrat în figura.
![]()
Un grafic obișnuit, în care, pe lângă filtrele disponibile ale mediastreamerului, sunt aplicate patru filtre artizanale F1…F4, de tipuri diferite, pe care le-ați creat demult și de care nu aveți îndoieli în privința corectitudinii. Cu toate acestea, să presupunem că în unele dintre ele există o scurgere de memorie. Rulând programul nostru cu analiza de supraveghere, din raportul său aflăm că un anumit filtru a solicitat o cantitate de memorie și nu a restituit-o în heap de N ori. Este ușor de intuit că va exista o referință la funcțiile interne ale filtrului tip MS_VOID_SOURCE. Sarcina sa este de a prelua memorie din heap. Alte filtre ar trebui să o restituie înapoi. Așadar, vom descoperi faptul că există o scurgere.
Pentru a determina pe ce secțiune a conveierului a avut loc inactivitatea care a dus la scurgerea de memorie, se propune introducerea unui filtru suplimentar, care pur și simplu transferă mesajele de la intrare la ieșire, dar în același timp crează o copiere "greu" a mesajului de intrare, apoi eliminând complet mesajul intrat. Vom numi acest filtru izolator. Considerăm că, fiind simplu, scurgerea în el este exclusă. Și un alt avantaj — dacă îl adăugăm în orice loc din graficul nostru, acest lucru nu va afecta funcționarea schemei. Vom reprezenta filtrul-izolator sub formă de cerc cu contur dublu.
Activăm izolatorul imediat după filtrul voidsource:
![]()
Repornim din nou programul cu analizatorul și vedem că, de această dată, analizatorul va da vina pe izolator. Fiindcă acesta creează acum blocuri de date, care apoi se pierd din cauza unui filtru (sau filtrelor) nesăbuite. Pasul următor este să mutăm izolatorul în lanț la dreapta, cu un filtru, și să repornim analiza. Astfel, mutând pas cu pas izolatorul spre dreapta, vom obține o situație în care, în raportul următor al analizatorului, numărul de blocuri de memorie „scurse” se va reduce. Asta înseamnă că, în acest pas, izolatorul s-a plasat în lanț imediat după filtrul problematic. Dacă filtrul
Implementarea filtrului-izolator
Implementarea izolatorului arată similar cu un filtru obișnuit. Titlu:
/* Файл 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
Filtrul propriu-zis:
/* Файл 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)
Metoda de înlocuire a funcțiilor de gestionare a memoriei
Pentru analize mai detaliate, mediastreamer-ul oferă posibilitatea de a înlocui funcțiile de acces la memorie cu propriile funcții care, pe lângă funcția principală, vor înregistra "Cine, unde și de ce". Trei funcții sunt înlocuite. Aceasta se face în următorul mod:
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);Această opțiune este utilă în situațiile în care analizatorul încetinește atât de mult funcționarea filtrelor încât afectează sistemul în care este integrată schema noastră. În această situație, trebuie să renunțăm la analizator și să utilizăm înlocuirea funcțiilor de gestionare a memoriei.
Am analizat algoritmul de acțiune pentru un grafic simplu, fără ramificații. Dar această abordare se poate aplica și în alte cazuri, desigur cu complexitate crescută, însă conceptul va rămâne același.
În articolul următor, vom examina problema evaluării sarcinii pe ticker și metodele de combatere a supraîncărcării computaționale în mediastreamer.
Sursa: habr.com
