Explorando el motor VoIP de Mediastreamer2. Parte 12

El material del artículo está tomado de mi canal zen.

Explorando el motor VoIP de Mediastreamer2. Parte 12

En el pasado статье, prometí considerar el problema de la estimación de la carga del teletipo y las formas de lidiar con la carga informática excesiva en el transmisor de medios. Pero decidí que sería más lógico cubrir los problemas de depuración de filtros artesanales relacionados con el movimiento de datos y solo entonces considerar los problemas de optimización del rendimiento.

Depuración de filtros artesanales

Después de que examinamos el mecanismo del movimiento de datos en un transmisor de medios en el artículo anterior, sería lógico hablar sobre los peligros ocultos en él. Una de las características del principio de "flujo de datos" es que la asignación de memoria del montón ocurre en los filtros ubicados en las fuentes del flujo de datos, y los filtros ubicados al final de la ruta del flujo ya desasignan la memoria con retorno al montón. Además, la creación de nuevos datos y su destrucción pueden ocurrir en algún lugar de los puntos intermedios. En general, la liberación de memoria la realiza un filtro diferente al que creó el bloque de datos.

Desde el punto de vista del monitoreo transparente de la memoria, sería razonable que el filtro, al recibir un bloque de entrada, lo destruya inmediatamente después del procesamiento, libere la memoria y coloque un bloque recién creado con datos de salida en la salida. En este caso, la fuga de memoria en el filtro podría rastrearse fácilmente: si el analizador detecta una fuga en el filtro, entonces el filtro que lo sigue no destruye correctamente los bloques entrantes y hay un error en él. Pero desde el punto de vista de mantener un alto rendimiento, este enfoque para trabajar con bloques de datos no es productivo: conduce a una gran cantidad de operaciones para asignar/liberar memoria para bloques de datos sin ningún escape útil.

Por eso, los filtros de media streamer, para no ralentizar el procesamiento de datos, utilizan funciones que crean copias ligeras al copiar mensajes (hablamos de ellas en un artículo anterior). Estas funciones solo crean una nueva copia del encabezado del mensaje "adjuntando" el bloque de datos del mensaje "antiguo" copiado. Como resultado, se adjuntan dos encabezados a un bloque de datos y se incrementa el contador de referencia en el bloque de datos. Pero se verá como dos mensajes. Puede haber más mensajes con un bloque de datos "público", por ejemplo, el filtro MS_TEE genera diez copias de este tipo a la vez, distribuyéndolas entre sus salidas. Si todos los filtros de la cadena funcionan correctamente, al final de la canalización, este recuento de referencias debería llegar a cero y se llamará a la función de desasignación de memoria: ms_libre(). Si la llamada no se produce, esta parte de la memoria ya no se devolverá al montón, es decir, él "filtra". El costo de usar copias ligeras es la pérdida de la capacidad de determinar fácilmente (como sería en el caso de usar copias regulares) en qué filtro gráfico se está perdiendo la memoria.

Dado que la responsabilidad de encontrar fugas de memoria en los filtros "nativos" recae en los desarrolladores del transmisor de medios, lo más probable es que no tenga que depurarlos. Pero con su filtro de elaboración, usted mismo es el saltamontes de su propia felicidad, y el tiempo que dedique a buscar fugas en su código dependerá de su precisión. Para reducir el tiempo de depuración, debemos observar las técnicas de localización de fugas al diseñar filtros. Además, puede suceder que la fuga se manifieste solo cuando se aplica el filtro en un sistema real, donde la cantidad de "sospechosos" puede ser enorme y el tiempo de depuración es limitado.

¿Cómo se manifiesta una pérdida de memoria?

Es lógico suponer que en la salida del programa parte superior mostrará un porcentaje creciente de memoria ocupada por su aplicación.

La manifestación externa consistirá en que en algún momento el sistema reaccionará lentamente al movimiento del mouse, redibujando lentamente la pantalla. También es posible que el registro del sistema crezca, ocupando espacio en el disco duro. En este caso, su aplicación comenzará a comportarse de manera extraña, no responderá a los comandos, no podrá abrir el archivo, etc.

Para identificar el hecho de una fuga, utilizaremos un analizador de memoria (en adelante, el analizador). Podría ser Valgrind (bien artículo al respecto) o integrado en el compilador gcc Desinfectante de memoria o algo mas. Si el analizador muestra que la fuga ocurre en uno de los filtros gráficos, significa que es hora de aplicar uno de los métodos que se describen a continuación.

Método de los tres pinos

Como se mencionó anteriormente, en caso de una pérdida de memoria, el analizador apuntará al filtro que solicitó la asignación de memoria del montón. Pero no señalará el filtro que "olvidó" devolverlo, que, de hecho, tiene la culpa. Por lo tanto, el analizador solo puede confirmar nuestros miedos, pero no señalar su raíz.

Para averiguar la ubicación del filtro "malo" en el gráfico, puede reducir el gráfico al número mínimo de nodos en los que el analizador aún detecta una fuga y ubicar el filtro problemático en los tres pines restantes.

Pero puede suceder que al reducir la cantidad de filtros en la columna, interrumpa el curso normal de interacción entre los filtros y otros elementos de su sistema y la fuga ya no aparecerá. En este caso, deberá trabajar con un gráfico de tamaño completo y utilizar el enfoque que se describe a continuación.

Método del aislador deslizante

Para simplificar la presentación, utilizaremos un gráfico que consta de una sola cadena de filtros. Ella se muestra en la imagen.

Explorando el motor VoIP de Mediastreamer2. Parte 12

Un gráfico ordinario, en el que, junto con filtros de transmisión de medios listos para usar, se utilizan cuatro filtros artesanales F1…F4, cuatro tipos diferentes que hizo hace mucho tiempo y no tiene dudas sobre su corrección. Sin embargo, suponga que varios de ellos tienen una fuga de memoria. Cuando ejecutamos nuestro programa de supervisión del analizador, aprendemos de su informe que cierto filtro solicitó una cierta cantidad de memoria y no la devolvió al montón N veces. Es fácil adivinar que habrá una referencia a las funciones de filtrado interno del tipo MS_VOID_SOURCE. Su tarea es tomar la memoria del montón. Otros filtros deberían devolverlo allí. Aquellos. encontraremos la fuga.

Para determinar en qué sección de la tubería se produjo la inactividad que condujo a una fuga de memoria, se propone introducir un filtro adicional que simplemente cambia los mensajes de entrada a salida, pero al mismo tiempo crea una copia no ligera de la entrada. mensaje en una copia "pesada" normal, luego eliminando completamente el mensaje que llegó a la entrada. Llamaremos a tal filtro un aislante. Creemos que dado que el filtro es simple, se excluyen las fugas. Y una propiedad positiva más: si la agregamos a cualquier lugar de nuestro gráfico, esto no afectará el funcionamiento del circuito de ninguna manera. Representaremos el filtro aislante como un círculo con un doble contorno.

Habilite el aislador justo después del filtro voidsource:
Explorando el motor VoIP de Mediastreamer2. Parte 12

Ejecutamos el programa con el analizador nuevamente, y vemos que esta vez, el analizador echará la culpa al aislador. Después de todo, es él quien ahora crea bloques de datos, que luego se pierden por un filtro negligente desconocido (o filtros). El siguiente paso es desplazar el aislador a lo largo de la cadena hacia la derecha, un filtro, y comenzar de nuevo el análisis. Entonces, paso a paso, moviendo el aislador hacia la derecha, obtenemos una situación en la que disminuye la cantidad de bloques de memoria "filtrados" en el siguiente informe del analizador. Esto significa que en este paso el aislador terminó en la cadena inmediatamente después del filtro problemático. Si solo había un filtro "malo", la fuga desaparecerá por completo. Por lo tanto, localizamos el filtro problemático (o uno de varios). Habiendo "arreglado" el filtro, podemos continuar moviendo el aislador hacia la derecha a lo largo de la cadena hasta que las fugas de memoria se eliminen por completo.

Implementación de un filtro aislador

La implementación del aislador se parece a un filtro normal. Archivo de cabecera:

/* Файл 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 en sí:

/* Файл 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 una investigación más sutil, el transmisor de medios brinda la capacidad de reemplazar las funciones de acceso a la memoria con las suyas propias, que, además del trabajo principal, arreglarán "Quién, dónde y por qué". Tres funciones están siendo reemplazadas. 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 característica viene al rescate en los casos en que el analizador ralentiza tanto los filtros que se interrumpe el funcionamiento del sistema en el que está construido nuestro circuito. En tal situación, debe abandonar el analizador y utilizar la sustitución de funciones de memoria.

Hemos considerado un algoritmo de acciones para un grafo simple que no contiene ramas. Pero este enfoque se puede aplicar a otros casos, por supuesto con complicaciones, pero la idea sigue siendo la misma.

En el próximo artículo, veremos el problema de la estimación de la carga de teletipos y cómo lidiar con la carga informática excesiva en el transmisor de medios.

Fuente: habr.com

Añadir un comentario