Материалът на статията е взет от моя .

В миналото , обещах да разгледам въпроса за оценка на натоварването на тикера и начините за противодействие на прекомерното изчислително натоварване в медиастримера. Но реших, че ще бъде логично първо да осветя въпросите за отстраняване на проблеми с крафтовите филтри, свързани с прехвърлянето на данни, и след това да разгледам въпросите за оптимизиране на производителността.
Отстраняване на проблеми с крафтовите филтри
След като в предишната статия разгледахме механизма за прехвърляне на данни в медиастримера, ще бъде логично да говорим за скритите в него опасности. Една от особеностите на принципа "data flow" е, че заделянето на памет от купчината се извършва в филтрите, които се намират в началото на потока данни, а освобождаването на паметта с връщането обратно в купчината се прави от филтрите, разположени в края на пътя на потока. Освен това, създаването и унищожаването на нови данни може да се случи на някои междинни точки. В общия случай, освобождаването на паметта не се извършва от същия филтър, който е създал блока данни.
От гледна точка на прозрачно мониториране на паметта, би било разумно, ако филтърът, получавайки входен блок, след обработка веднага го унищожаваше с освобождаване на паметта, а на изхода представяше новосъздаден блок с изходните данни. В този случай, изтичането на памет в филтъра лесно би могло да се проследи — ако анализаторът открие изтичане в филтъра, значи следващият филтър не унищожава входящите блокове надлежно и грешката е в него. Но от гледна точка на поддържането на висока производителност, такъв подход към работата с блокове данни не е продуктивен — той води до голямо количество операции по заделяне/освобождаване на памет под блокове данни без никаква полезна продукция.
По тази причина филтрите на медиастримера, за да не забавят обработката на данните, при копиране на съобщения използват функции, които създават леки копия (разказвахме за тях в предишната статия). Тези функции само създават нов екземпляр на заглавието на съобщението, "привързвайки" към него блок данни от копираното "старо" съобщение. В резултат на това, към един блок данни са свързани два заглавия и се извършва инкрементиране на брояча на референции в блока данни. Но това ще изглежда като две съобщения. Съобщения с такъв "обществен" блок данни може да има и повече, например филтърът MS_TEE генерира веднага десет такива леки копия, разпределяйки ги по своите изходи. При правилна работа на всички филтри в веригата, в края на конвейера този брояч на референции трябва да достигне нула и ще бъде извикана функцията за освобождаване на паметта: ms_free(). Ако извикването не се случи, то значи този блок памет вече няма да се върне в купчината, т.е. той "ще изтече". Цената за използването на леките копия е загубата на възможността лесно да установите (както би било в случая с използването на обикновени копия) в кой филтър графът изтича памет.
Тъй като отговорността за намиране на изтичанията на паметта в "родните" филтри лежи на разработчиците на медиастримера, е малко вероятно да се наложи да ги отстранявате. Но с вашия ръчно изработен филтър — вие сами сте ковач на собственото си щастие и от внимателността ви ще зависи времето, което ще прекарате в търсене на изтичания в кода си. За да съкратите времето на митарства с отстраняването на грешки, трябва да разгледаме методите за локализиране на изтичания при разработката на филтри. Освен това, може да се окаже, че изтичането се проявява само при прилагането на филтъра в реална система, където броят на "подозрителните" може да бъде огромен, а времето за отстраняване на грешки ограничено.
Как се проявява изтичането на памет?
Логично е да предположим, че в изхода на програмата top ще се показва нарастващ процент памет, заета от вашето приложение.
Външният израз ще се състои в това, че в някакъв момент системата ще започне бавно да реагира на движението на мишката, бавно да обновява екрана. Може също така да се увеличи системният лог, което ще изяде място на твърдия диск. В същото време вашето приложение ще започне да се държи странно, няма да отговаря на команди, няма да може да отвори файлове и т.н.
За да установим факта за възникване на теч на памет, ще използваме анализатор на паметта (по-долу анализатор). Това може да бъде Valgrind (добър за него) или вграден в компилатора gcc или нещо друго. Ако анализаторът покаже, че течът се появява в един от филтрите на графа, това означава, че е време да приложим един от методите, описани по-долу.
Метод на трите сосни
Както вече беше казано, при теч на памет анализаторът ще посочи филтър, който е поискал выделение на памет от купчина. Но няма да посочи филтъра, който "е забравил" да я върне, който всъщност е виновен. По този начин анализаторът може само да потвърди нашите опасения, но не може да посочи корена им.
За да установите местоположението на "недостойния" филтър в графа, можете да се насочите към съкращаване на графа до минимален брой възли, при които анализаторът все още открива теч и в оставащите три сосни да локализирате проблемния филтър.
Но може да се случи така, че при съкращаване на броя на филтрите в графа да нарушите обичайния ход на взаимодействие на филтрите с другите елементи на вашата система и течът да престане да се проявява. В този случай ще трябва да работите с пълноразмерния граф и да използвате подхода, изложен по-долу.
Метод на плъзгащия се изолатор
За опростяване на изложението ще използваме граф, който се състои от една верига филтри. Той е изобразен на рисунка.
![]()
Обикновен граф, в който наред с готовите филтри на медийните стриймери са приложени четири крафтови филтри F1…F4, четири различни типа, които сте направили преди време и нямате съмнения за тяхната коректност. Все пак, да предположим, че в някои от тях има изтичане на памет. При пускане на нашата програма с анализатора, от неговия отчет ще разберем, че определен филтър е поискал известно количество памет и не я е върнал в купчината N-колко пъти. Лесно можем да се досетим, че ще има връзка с вътрешни функции на филтъра тип MS_VOID_SOURCE. Неговата задача е да взима памет от купчината. Да я връщат обратно трябва други филтри. Т.е. ще открием факт на изтичане.
За да определим в кой участък на конвейера е настъпило бездействие, довело до изтичане на памет, предлага се да въведем допълнителен филтър, който просто пренася съобщенията от входа до изхода, но в същото време създава не лека, а нормална "тежка" копия на входното съобщение, след което напълно изтрива съобщението, постъпило на входа. Ще наричаме такъв филтър изолатор. Приемаме, че тъй като филтърът е прост, изтичането в него е изключено. И още едно положително свойство — ако го добавим на всяко място в нашия граф, това няма да се отрази на работата на схемата. Ще изобразим филтър-изолатор във вид на кръг с двойна обиколка.
Включваме изолатора веднага след филтъра voidsource:
![]()
Отново пускаме програмата с анализатора и виждаме, че този път анализаторът ще наложи вината на изолятора. Нали той сега създава блокове данни, които после се губят от неизвестен небрежен филтър (или филтри). Следващата стъпка е да преместим изолятора по веригата надясно, на един филтър и отново да пуснем анализа. Така, стъпка по стъпка, премествайки изолятора надясно, ще получим ситуация, в която в следващия отчет на анализатора количеството "изтекли" блокове памет ще намалее. Това означава, че на тази стъпка изолаторът е попаднал в веригата веднага след проблемния филтър. Ако "лошият" филтър е бил един, то изтичането и изобщо ще изчезне. Така локализирахме проблемния филтър (или един от няколкото). "Починив" филтъра, можем да продължим да движим изолятора надясно по веригата до пълна победа над изтичанията на памет.
Реализация на филтър-изолатор
Реализация на изолатора изглежда като обикновен филтър. Хедър файл:
/* Файл 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
Самият филтър:
/* Файл 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)
Метод за замяна на функции за управление на паметта
За по-дълбочинни изследвания, в медиастримера е предвидена възможност за замяна на функции за достъп до паметта с ваши собствени, които ще регистрират "Кой, къде и защо" освен основната работа. Заместят се три функции. Това се прави по следния начин:
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);Тази възможност е полезна в случаи, когато анализаторът забавя работата на филтрите толкова, че нарушава функционирането на системата, в която е внедрена нашата схема. В такава ситуация се налага да се откажем от анализатора и да използваме замяна на функциите за работа с паметта.
Разгледахме алгоритъма на действие за прост граф без разклонения. Но този подход може да се приложи и за други случаи, разбира се с усложняване, но идеята остава същата.
В следващата статия ще разгледаме въпроса за оценка на натоварването на тикера и начините за справяне с прекомерната изчислителна натовареност в медиастримера.
Източник: habr.com
