Explorando o mecanismo VoIP do Mediastreamer2. Parte 12

O material do artigo foi retirado do meu canal zen.

Explorando o mecanismo VoIP do Mediastreamer2. Parte 12

No passado статье, prometi considerar a questão da avaliação da carga no ticker e formas de combater a carga excessiva de computação no streamer de mídia. Mas decidi que seria mais lógico cobrir as questões de depuração de filtros artesanais relacionados à movimentação de dados e só então considerar questões de otimização de desempenho.

Depurando filtros artesanais

Depois de examinarmos o mecanismo de movimentação de dados em um streamer de mídia no artigo anterior, seria lógico falar sobre os perigos ocultos nele. Uma das características do princípio do “fluxo de dados” é que a memória é alocada do heap em filtros localizados na origem do fluxo de dados, e a memória é liberada e devolvida ao heap por filtros localizados no final do fluxo caminho. Além disso, a criação de novos dados e sua destruição podem ocorrer em algum ponto intermediário. Em geral, a liberação de memória é realizada por um filtro diferente daquele que criou o bloco de dados.

Do ponto de vista do monitoramento transparente da memória, seria razoável que o filtro, ao receber um bloco de entrada, o destruísse imediatamente após o processamento, liberando a memória, e produzisse um bloco recém-criado com os dados de saída. Nesse caso, um vazamento de memória no filtro seria facilmente rastreado - se o analisador detectasse um vazamento no filtro, o próximo filtro não destruiria os blocos recebidos corretamente e haveria um erro nele. Mas do ponto de vista de manter o alto desempenho, esta abordagem para trabalhar com blocos de dados não é produtiva - leva a um grande número de operações para alocar/liberar memória para blocos de dados sem qualquer saída útil.

Por isso, os filtros de streamer de mídia, para não retardar o processamento dos dados, utilizam funções que criam cópias fáceis na hora de copiar mensagens (falamos sobre eles no artigo anterior). Essas funções apenas criam uma nova instância do cabeçalho da mensagem “anexando” a ela um bloco de dados da mensagem “antiga” que está sendo copiada. Como resultado, dois cabeçalhos são anexados a um bloco de dados e o contador de referência no bloco de dados é incrementado. Mas parecerão duas mensagens. Pode haver mais mensagens com esse bloco de dados “socializado”, por exemplo, o filtro MS_TEE gera uma dúzia dessas cópias leves de uma só vez, distribuindo-as entre suas saídas. Se todos os filtros da cadeia funcionarem corretamente, ao final do pipeline esse contador de referência deverá chegar a zero e a função de liberação de memória será chamada: ms_free(). Se a chamada não ocorrer, esse pedaço de memória não será mais retornado ao heap, ou seja, vai "vazar". O preço do uso de cópias leves é a perda da capacidade de determinar facilmente (como seria o caso com cópias normais) qual filtro gráfico está vazando memória.

Como os desenvolvedores do streamer de mídia são responsáveis ​​por encontrar vazamentos de memória em filtros nativos, você provavelmente não precisará depurá-los. Mas com seu filtro artesanal, você é o gafanhoto de sua própria felicidade, e o tempo gasto procurando vazamentos em seu código dependerá de sua precisão. Para reduzir o tempo de depuração, precisamos observar as técnicas de detecção de vazamentos ao desenvolver filtros. Além disso, pode acontecer que o vazamento se manifeste apenas quando o filtro for aplicado em um sistema real, onde o número de “suspeitos” pode ser enorme e o tempo de depuração limitado.

Como um vazamento de memória se manifesta?

É lógico supor que na saída do programa topo mostrará a porcentagem crescente de memória ocupada pelo seu aplicativo.

A manifestação externa será que em algum momento o sistema começará a responder lentamente ao movimento do mouse e redesenhar lentamente a tela. Também é possível que o log do sistema cresça, ocupando espaço no disco rígido. Nesse caso, seu aplicativo começará a se comportar de maneira estranha, não responderá aos comandos, não conseguirá abrir um arquivo, etc.

Para detectar a ocorrência de um vazamento, utilizaremos um analisador de memória (doravante denominado analisador). Poderia ser Valgrind (bom artigo sobre isso) ou embutido no compilador gcc Sanitizador de memória ou qualquer outra coisa. Se o analisador mostrar que ocorre um vazamento em um dos filtros gráficos, isso significa que é hora de aplicar um dos métodos descritos abaixo.

Método dos Três Pinheiros

Conforme mencionado acima, se houver vazamento de memória, o analisador apontará para o filtro que solicitou a alocação de memória do heap. Mas não vai apontar o filtro que “esqueceu” de devolvê-lo, que, na verdade, é o culpado. Assim, o analisador só pode confirmar os nossos medos, mas não indicar a sua raiz.

Para descobrir a localização do filtro “ruim” no gráfico, você pode reduzir o gráfico ao número mínimo de nós nos quais o analisador ainda detecta um vazamento e localizar o filtro problemático nos três pinos restantes.

Mas pode acontecer que, ao reduzir o número de filtros no gráfico, você interrompa o curso normal de interação entre os filtros e outros elementos do seu sistema e o vazamento não apareça mais. Nesse caso, você terá que trabalhar com um gráfico em tamanho real e usar a abordagem descrita abaixo.

Método de isolador deslizante

Para simplificar a apresentação, usaremos um gráfico que consiste em uma cadeia de filtros. Ela é mostrada na foto.

Explorando o mecanismo VoIP do Mediastreamer2. Parte 12

Um gráfico regular no qual, junto com filtros de streamer de mídia prontos, são usados ​​quatro filtros artesanais F1...F4, de quatro tipos diferentes, que você fez há muito tempo e não tem dúvidas sobre sua correção. No entanto, vamos supor que vários deles tenham vazamentos de memória. Executando nosso programa para monitorar o analisador, aprendemos com seu relatório que um determinado filtro solicitou uma certa quantidade de memória e não a retornou ao heap N vezes. Você pode facilmente adivinhar que haverá um link para as funções de filtro interno do tipo MS_VOID_SOURCE. Sua tarefa é retirar a memória da pilha. Outros filtros devem devolvê-lo para lá. Aqueles. detectaremos o fato do vazamento.

Para determinar em qual seção do pipeline houve inação que levou a um vazamento de memória, propõe-se a introdução de um filtro adicional que simplesmente muda as mensagens da entrada para a saída, mas ao mesmo tempo cria uma cópia da mensagem de entrada que não é leve , mas sim um normal “pesado”, então apaga completamente a mensagem recebida na entrada. Chamaremos esse filtro de isolante. Acreditamos que por o filtro ser simples não há vazamento nele. E mais uma propriedade positiva - se adicionarmos em qualquer lugar do nosso gráfico, isso não afetará de forma alguma o funcionamento do circuito. Descreveremos o filtro-isolador na forma de um círculo com circuito duplo.

Ligamos o isolador imediatamente após o filtro voidsource:
Explorando o mecanismo VoIP do Mediastreamer2. Parte 12

Executamos novamente o programa com o analisador e vemos que desta vez o analisador culpará o isolador. Afinal, é ele quem agora cria blocos de dados, que depois são perdidos por um filtro (ou filtros) desconhecido e descuidado. O próximo passo é mover o isolador ao longo da cadeia para a direita, por um filtro, e iniciar a análise novamente. Assim, movendo passo a passo o isolador para a direita, teremos uma situação em que no próximo relatório do analisador o número de blocos de memória “vazados” diminuirá. Isso significa que nesta etapa o isolador estava no circuito imediatamente após o filtro com problema. Se houvesse apenas um filtro “ruim”, o vazamento desapareceria completamente. Assim, localizamos o filtro problemático (ou um dos vários). Depois de “consertar” o filtro, podemos continuar a mover o isolador para a direita ao longo da cadeia até eliminarmos completamente os vazamentos de memória.

Implementação de um filtro isolador

A implementação do isolador se parece com um filtro normal. Arquivo de cabeçalho:

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

O filtro em si:

/* Файл 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 para substituir funções de gerenciamento de memória

Para pesquisas mais sutis, o media streamer oferece a possibilidade de substituir as funções de acesso à memória pelas suas próprias, que, além do trabalho principal, registrarão “Quem, onde e por quê”. Três funções são substituídas. Isto se faz do seguinte modo:

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

Esse recurso auxilia nos casos em que o analisador retarda tanto o funcionamento dos filtros que o funcionamento do sistema no qual nosso circuito está construído é interrompido. Nessa situação, é necessário abandonar o analisador e utilizar a substituição de funções para trabalhar com memória.

Consideramos um algoritmo de ações para um gráfico simples que não contém ramificações. Mas esta abordagem pode ser aplicada a outros casos, claro que com mais complexidade, mas a ideia permanece a mesma.

No próximo artigo, veremos a questão de estimar a carga em um ticker e maneiras de combater a carga excessiva de computação em um streamer de mídia.

Fonte: habr.com

Adicionar um comentário