El material del artículo fue tomado de mi .

En el anterior , prometí abordar el tema de la evaluación de la carga en el ticker y las formas de combatir la carga computacional excesiva en el mediastreamer. Sin embargo, decidí que sería más lógico tratar los temas de depuración de filtros personalizados, relacionados con el movimiento de datos, y luego abordar las cuestiones de optimización del rendimiento.
Depuración de filtros personalizados
Después de que en el artículo anterior discutimos el mecanismo de movimiento de datos en el mediastreamer, sería lógico hablar sobre los peligros que se esconden en él. Una de las características del principio de "flujo de datos" es que la asignación de memoria desde el heap se realiza en los filtros que están al inicio del flujo de datos, mientras que la liberación de memoria con el retorno al heap la realizan los filtros situados al final del camino del flujo. Además, la creación y destrucción de nuevos datos puede ocurrir en puntos intermedios. En general, la liberación de memoria no la realiza el filtro que creó el bloque de datos.
Desde el punto de vista de un monitoreo transparente de la memoria, sería razonable que el filtro, al recibir un bloque de entrada, después de procesarlo lo destruyera inmediatamente liberando memoria, y en la salida presentara un nuevo bloque con los datos de salida. En este caso, la fuga de memoria en el filtro sería fácil de rastrear: si el analizador detecta una fuga en el filtro, significa que el siguiente filtro no está destruyendo los bloques de entrada de manera adecuada, y la error está en él. Pero desde el punto de vista de mantener un alto rendimiento, este enfoque para trabajar con bloques de datos no es productivo; lleva a un gran número de operaciones de asignación/liberación de memoria para los bloques de datos sin ningún tipo de salida útil.
Por esta razón, los filtros del mediastreamer, para no ralentizar el procesamiento de datos, al copiar mensajes utilizan funciones que crean copias ligeras (de las que hablamos en el artículo anterior). Estas funciones solo crean una nueva instancia del encabezado del mensaje "anexando" a él un bloque de datos del "viejo" mensaje copiado. Como resultado, a un bloque de datos se le asocian dos encabezados y se incrementa el contador de referencias en el bloque de datos. Pero esto se verá como dos mensajes. Puede haber más mensajes con un bloque de datos "compartido"; por ejemplo, el filtro MS_TEE genera inmediatamente una docena de estas copias ligeras, distribuyéndolas por sus salidas. Con un funcionamiento correcto de todos los filtros en la cadena, al final de la línea de producción, este contador de referencias debería alcanzar cero y se llamará a la función de liberación de memoria: ms_free(). Si no se llama, significa que este bloque de memoria no regresará al montón, es decir, "se filtrará". El precio por usar copias ligeras es la pérdida de la posibilidad de establecer fácilmente (como sería el caso con copias normales) en qué filtro se escapa la memoria.
Dado que la responsabilidad de buscar fugas de memoria en los filtros "nativos" recae en los desarrolladores del mediastreamer, lo más probable es que no tengas que depurarlos. Pero con tu filtro artesanal, tú eres el arquitecto de tu propia fortuna y la atención al detalle determinará el tiempo que pasarás buscando fugas en tu código. Para acortar tu tiempo de sufrimiento con la depuración, debemos considerar técnicas de localización de fugas al desarrollar filtros. Además, puede suceder que una fuga se manifieste solo al aplicar el filtro en un sistema real, donde la cantidad de "sospechosos" puede ser enorme y el tiempo para depurar limitado.
¿Cómo se manifiesta una fuga de memoria?
Es lógico suponer que en la salida del programa top se mostrará un porcentaje creciente de memoria ocupada por tu aplicación.
La manifestación externa consistirá en que en algún momento el sistema reaccionará lentamente al movimiento del ratón, redibujará la pantalla lentamente. También puede crecer el registro del sistema, ocupando espacio en el disco duro. Mientras tanto, su aplicación comenzará a comportarse de manera extraña, no responderá a los comandos, no podrá abrir archivos, etc.
Para identificar si hay una fuga de memoria, utilizaremos un analizador de memoria (en adelante, el analizador). Esto puede ser Valgrind (bueno sobre él) o integrado en el compilador gcc o algo más. Si el analizador muestra que la fuga ocurre en uno de los filtros del grafo, significa que es hora de aplicar uno de los métodos descritos a continuación.
Método de las tres piñas
Como se mencionó anteriormente, en caso de una fuga de memoria, el analizador indicará el filtro que solicitó la asignación de memoria en el montón. Pero no indicará el filtro que "olvidó" devolverla, que en realidad es el culpable. De esta forma, el analizador puede confirmar nuestras sospechas, pero no señalar su origen.
Para determinar la ubicación del filtro "problemático" en el grafo, se puede reducir el grafo a la menor cantidad de nodos en la que el analizador todavía detecte la fuga y en las tres piñas restantes localizar el filtro problemático.
Pero puede suceder que al reducir el número de filtros en el grafo, rompa el flujo habitual de interacción de los filtros con otros elementos de su sistema y la fuga deje de manifestarse. En ese caso, será necesario trabajar con el grafo completo y utilizar el enfoque que se describe a continuación.
Método del aislante deslizante
Para simplificar la exposición, utilizaremos un grafo que consiste en una cadena de filtros. Se muestra en la figura.
![]()
Un gráfico simple, en el que junto a los filtros predeterminados del mediastreamer se aplican cuatro filtros artesanales F1…F4, de cuatro tipos diferentes, que hiciste hace tiempo y cuya corrección no dudas. Sin embargo, supongamos que en algunos de ellos hay una fuga de memoria. Al ejecutar nuestro programa de supervisión del analizador, a partir de su informe sabremos que un cierto filtro solicitó cierta cantidad de memoria y no la devolvió al montón N veces. Es fácil adivinar que habrá una referencia a funciones internas del filtro tipo MS_VOID_SOURCE. Su tarea es tomar memoria del montón. Otros filtros deben devolverla. Es decir, descubriremos el hecho de la fuga.
Para determinar en qué parte de la cadena de producción ocurrió la inactividad que llevó a la fuga de memoria, se propone introducir un filtro adicional que simplemente transfiere mensajes de entrada a salida, pero al mismo tiempo crea una copia pesada, en lugar de una ligera, del mensaje de entrada, eliminando completamente el mensaje entrante. Llamaremos a este filtro aislante. Suponemos que, dado que el filtro es simple, la fuga está excluida. Y otra característica positiva es que si lo añadimos en cualquier lugar de nuestro gráfico, no afectará el funcionamiento del esquema. Representaremos el filtro-aislante como un círculo con doble contorno.
Activamos el aislante justo después del filtro voidsource:
![]()
Nuevamente ejecutamos el programa con el analizador y vemos que esta vez, el analizador culpará al aislante. Después de todo, es él quien ahora crea bloques de datos que luego se pierden a causa de un filtro (o filtros) descuidado(s) desconocido(s). El siguiente paso es mover el aislante a lo largo de la cadena hacia la derecha, a un filtro y volver a ejecutar el análisis. Así, paso a paso, moviendo el aislante a la derecha, obtendremos una situación en la que en el próximo informe del analizador la cantidad de bloques de memoria "filtrados" disminuirá. Esto significa que en este paso el aislante se encontró en la cadena inmediatamente después del filtro problemático. Si el filtro "malo" era uno, la fuga desaparecerá por completo. Así, hemos localizado el filtro problemático (o uno de varios). Al "reparar" el filtro, podemos seguir moviendo el aislante a la derecha en la cadena hasta lograr una victoria completa sobre las fugas de memoria.
Implementación del filtro-aislante
La implementación del aislante se ve igual que un filtro común. Encabezado:
/* Файл 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
El filtro mismo:
/* Файл 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étodo de sustitución de funciones de gestión de memoria
Para investigaciones más detalladas, el mediastreamer tiene la opción de reemplazar las funciones de acceso a la memoria con las propias, que además de su función principal, registrarán "Quién, a dónde y por qué". Se sustituyen tres funciones. Esto se hace de la siguiente manera:
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);Esta opción es útil en casos en los que el analizador ralentiza el funcionamiento de los filtros de tal manera que interfiere con el funcionamiento del sistema en el que está integrada nuestra configuración. En tal situación, es necesario renunciar al analizador y utilizar la sustitución de las funciones de gestión de memoria.
Hemos revisado el algoritmo de acciones para un gráfico simple, que no contiene ramificaciones. Pero este enfoque también se puede aplicar a otros casos, por supuesto con más complejidad, pero la idea seguirá siendo la misma.
En el siguiente artículo, abordaremos la cuestión de la evaluación de la carga en el ticker y las formas de combatir la carga computacional excesiva en el mediastreamer.
Fuente: habr.com
