Poznawanie silnika Mediastreamer2 VoIP. Część 12

Materiał artykułu pochodzi z mojego kanał zen.

Poznawanie silnika Mediastreamer2 VoIP. Część 12

W przeszłości Artykuł, obiecałem rozważyć kwestię szacowania obciążenia ticker'a i sposobów radzenia sobie z nadmiernym obciążeniem obliczeniowym w streamerze multimediów. Uznałem jednak, że bardziej logiczne będzie omówienie kwestii debugowania filtrów rzemieślniczych związanych z ruchem danych, a dopiero potem rozważenie kwestii optymalizacji wydajności.

Debugowanie filtrów rzemieślniczych

Po tym, jak w poprzednim artykule przeanalizowaliśmy mechanizm przenoszenia danych w streamerze multimediów, logiczne byłoby omówienie kryjących się w nim niebezpieczeństw. Jedną z cech zasady „przepływu danych” jest to, że alokacja pamięci ze sterty następuje w filtrach znajdujących się u źródeł przepływu danych, a filtry znajdujące się na końcu ścieżki przepływu już zwalniają pamięć ze zwrotem do sterty. Ponadto tworzenie nowych danych i ich niszczenie może nastąpić gdzieś w punktach pośrednich. Ogólnie rzecz biorąc, zwolnienie pamięci jest wykonywane przez filtr inny niż ten, który utworzył blok danych.

Z punktu widzenia przejrzystego monitoringu pamięci zasadne byłoby, aby filtr po odebraniu bloku wejściowego natychmiast po przetworzeniu go zniszczył, zwalniając pamięć, a na wyjściu umieścił nowo utworzony blok z danymi wyjściowymi. W takim przypadku wyciek pamięci w filtrze można było łatwo namierzyć — jeśli analizator wykrył wyciek w filtrze, to następujący po nim filtr nie niszczy poprawnie przychodzących bloków i jest w nim błąd. Ale z punktu widzenia utrzymania wysokiej wydajności takie podejście do pracy z blokami danych nie jest produktywne - prowadzi do dużej liczby operacji przydzielania / zwalniania pamięci dla bloków danych bez żadnego użytecznego wyczerpania.

Z tego powodu media streamer filtry, aby nie spowalniać przetwarzania danych, podczas kopiowania wiadomości wykorzystują funkcje tworzące lekkie kopie (mówiliśmy o nich w poprzednim artykule). Te funkcje tworzą tylko nową kopię nagłówka wiadomości poprzez „dołączenie” do niej bloku danych ze skopiowanej „starej” wiadomości. W rezultacie do jednego bloku danych dołączane są dwa nagłówki, a licznik referencji w bloku danych jest zwiększany. Ale będzie to wyglądać jak dwie wiadomości. Komunikatów z takim „publicznym” blokiem danych może być więcej, np. filtr MS_TEE generuje naraz dziesięć takich lekkich kopii, rozdzielając je między swoje wyjścia. Jeśli wszystkie filtry w łańcuchu działają poprawnie, pod koniec potoku ta liczba odwołań powinna osiągnąć zero, a funkcja zwalniania pamięci zostanie wywołana: ms_free(). Jeśli wywołanie nie nastąpi, to ten fragment pamięci nie będzie już zwracany na stertę, tj. on „przecieka”. Kosztem korzystania z lekkich kopii jest utrata możliwości łatwego określenia (tak jak byłoby to w przypadku zwykłych kopii), w którym filtrze grafowym przecieka pamięć.

Ponieważ odpowiedzialność za znalezienie wycieków pamięci w filtrach „natywnych” spoczywa na twórcach streamera multimediów, najprawdopodobniej nie będziesz musiał ich debugować. Ale dzięki filtrowi rzemieślniczemu sam jesteś konikiem polnym własnego szczęścia, a czas spędzony na wyszukiwaniu wycieków w kodzie będzie zależał od Twojej dokładności. Aby skrócić czas debugowania, podczas projektowania filtrów musimy przyjrzeć się technikom lokalizacji wycieków. Dodatkowo może się zdarzyć, że wyciek ujawni się dopiero przy zastosowaniu filtra w rzeczywistym systemie, gdzie liczba „podejrzanych” może być ogromna, a czas na debugowanie ograniczony.

Jak objawia się wyciek pamięci?

Logiczne jest założenie, że na wyjściu programu Top pokaże rosnący procent pamięci zajmowanej przez twoją aplikację.

Zewnętrzna manifestacja polegać będzie na tym, że w pewnym momencie system będzie powoli reagował na ruch myszki, powoli przerysowując ekran. Możliwe jest również, że dziennik systemowy powiększy się, zajmując miejsce na dysku twardym. W takim przypadku Twoja aplikacja zacznie dziwnie się zachowywać, nie będzie odpowiadać na polecenia, nie będzie mogła otworzyć pliku itp.

Aby zidentyfikować fakt wycieku, użyjemy analizatora pamięci (zwanego dalej analizatorem). Mogłoby być valgrind (Dobry artykuł o tym) lub wbudowane w kompilator gcc Odkażacz do pamięci albo coś innego. Jeśli analizator wykaże, że wyciek występuje w jednym z filtrów grafowych, oznacza to, że czas zastosować jedną z opisanych poniżej metod.

Metoda Trzech Sosen

Jak wspomniano powyżej, w przypadku wycieku pamięci analizator wskaże filtr, który zażądał przydziału pamięci ze sterty. Ale nie wskaże filtra, który „zapomniał” go zwrócić, co w rzeczywistości jest winne. Tym samym analizator może jedynie potwierdzić nasze obawy, ale nie wskazać ich źródła.

Aby znaleźć położenie „złego” filtra na wykresie, można przejść przez zmniejszenie wykresu do minimalnej liczby węzłów, przy których analizator nadal wykrywa wyciek i zlokalizować problematyczny filtr w pozostałych trzech sosnach.

Ale może się zdarzyć, że zmniejszając ilość filtrów w kolumnie zakłócisz normalny przebieg interakcji pomiędzy filtrami a innymi elementami Twojego systemu i wyciek już się nie pojawi. W takim przypadku będziesz musiał pracować z pełnowymiarowym wykresem i zastosować podejście opisane poniżej.

Metoda izolatora ślizgowego

Dla uproszczenia prezentacji posłużymy się grafem składającym się z pojedynczego łańcucha filtrów. Jest ona pokazana na zdjęciu.

Poznawanie silnika Mediastreamer2 VoIP. Część 12

Zwykły wykres, w którym wraz z gotowymi filtrami streamera mediów zastosowano cztery filtry rzemieślnicze F1…F4, cztery różne typy, które zrobiłeś dawno temu i nie masz wątpliwości co do ich poprawności. Załóżmy jednak, że kilka z nich ma wyciek pamięci. Uruchamiając nasz program nadzorujący analizator, dowiadujemy się z jego raportu, że pewien filtr zażądał określonej ilości pamięci i nie zwrócił jej na stertę N razy. Nietrudno się domyślić, że znajdzie się tam odwołanie do wewnętrznych funkcji filtrujących typu MS_VOID_SOURCE. Jego zadaniem jest pobranie pamięci ze sterty. Inne filtry powinny go tam zwrócić. Te. znajdziemy przeciek.

W celu ustalenia, w którym odcinku potoku wystąpił brak aktywności, który doprowadził do wycieku pamięci, proponuje się wprowadzenie dodatkowego filtra, który po prostu przesuwa komunikaty z wejścia na wyjście, ale jednocześnie tworzy niejasną kopię wejścia wiadomość do normalnej „ciężkiej” kopii, a następnie całkowicie usuwając wiadomość, która dotarła do wejścia. Taki filtr nazwiemy izolatorem. Uważamy, że ponieważ filtr jest prosty, wyciek w nim jest wykluczony. I jeszcze jedna pozytywna właściwość - jeśli dodamy ją w dowolne miejsce naszego wykresu, to nie wpłynie to w żaden sposób na działanie obwodu. Przedstawimy filtr izolatora jako okrąg z podwójnym konturem.

Włącz izolator zaraz po filtrze voidsourse:
Poznawanie silnika Mediastreamer2 VoIP. Część 12

Ponownie uruchamiamy program z analizatorem i widzimy, że tym razem analizator zrzuci winę na izolator. W końcu to on tworzy teraz bloki danych, które następnie są tracone przez nieznany niedbały filtr (lub filtry). Kolejnym krokiem jest przesunięcie izolatora wzdłuż łańcucha w prawo o jeden filtr i ponowne rozpoczęcie analizy. I tak, krok po kroku, przesuwając izolator w prawo, otrzymujemy sytuację, w której liczba „wycieków” bloków pamięci w kolejnym raporcie analizatora maleje. Oznacza to, że na tym etapie izolator znalazł się w łańcuchu bezpośrednio za problematycznym filtrem. Jeśli był tylko jeden „zły” filtr, wyciek całkowicie zniknie. W ten sposób zlokalizowaliśmy problematyczny filtr (lub jeden z kilku). Po „naprawieniu” filtra możemy dalej przesuwać izolator w prawo wzdłuż łańcucha, aż do całkowitego wyeliminowania wycieków pamięci.

Implementacja filtra izolującego

Implementacja izolatora wygląda jak normalny filtr. Plik nagłówkowy:

/* Файл 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

Sam filtr:

/* Файл 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)

Sposób zastępowania funkcji zarządzania pamięcią

W przypadku bardziej subtelnych badań streamer multimediów zapewnia możliwość zastąpienia funkcji dostępu do pamięci własnymi, które oprócz głównej pracy rejestrują „Kto, gdzie i dlaczego”. Trzy funkcje są zastępowane. Odbywa się to w następujący sposób:

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);

Funkcja ta przychodzi z ratunkiem w przypadkach, gdy analizator spowalnia filtry na tyle, że praca układu, w którym zbudowany jest nasz układ, zostaje zakłócona. W takiej sytuacji trzeba zrezygnować z analizatora i skorzystać z funkcji podstawiania pamięci.

Rozważaliśmy algorytm działań dla prostego grafu, który nie zawiera rozgałęzień. Ale to podejście można zastosować w innych przypadkach, oczywiście z komplikacjami, ale idea pozostaje ta sama.

W kolejnym artykule przyjrzymy się zagadnieniu szacowania obciążenia tickera oraz temu, jak radzić sobie z nadmiernym obciążeniem obliczeniowym w streamerze multimediów.

Źródło: www.habr.com

Dodaj komentarz