探索 Mediastreamer2 VoIP 引擎。 第 12 部分

文章素材取自我 禅频道.

探索 Mediastreamer2 VoIP 引擎。 第 12 部分

在最后 文章,我承诺考虑评估股票代码负载的问题以及解决媒体流媒体中过多计算负载的方法。 但我认为更合乎逻辑的是,先讨论与数据移动相关的调试工艺过滤器问题,然后再考虑性能优化问题。

调试工艺过滤器

在我们了解了上一篇文章中在媒体流媒体中移动数据的机制之后,我们就可以顺理成章地讨论其中隐藏的危险了。 “数据流”原理的特点之一是,内存是在位于数据流源头的过滤器中从堆中分配的,而内存则由位于数据流末尾的过滤器释放并返回到堆中小路。 此外,新数据的创建和销毁可能发生在两者之间。 一般来说,内存释放是由与创建数据块的过滤器不同的过滤器执行的。

从透明内存监控的角度来看,过滤器在接收到输入块后,在处理后立即销毁它,释放内存,并输出带有输出数据的新创建的块是合理的。 在这种情况下,很容易追踪过滤器中的内存泄漏 - 如果分析器检测到过滤器中的泄漏,则下一个过滤器不会正确销毁传入的块,并且其中存在错误。 但从保持高性能的角度来看,这种处理数据块的方法效率不高——它会导致大量为数据块分配/释放内存的操作,而没有任何有用的输出。

因此,媒体流过滤器为了不减慢数据处理速度,在复制消息时使用创建简单副本的功能(我们在上一篇文章中讨论过它们)。 这些函数仅通过将复制的“旧”消息中的数据块“附加”到消息头来创建消息头的新实例。 结果,两个标头被附加到一个数据块,并且数据块中的引用计数器递增。 但它看起来像两条消息。 具有这种“社交化”数据块的消息可以更多,例如,MS_TEE 过滤器一次生成十几个这样的轻副本,并将它们分发到其输出中。 如果链中的所有过滤器都正确运行,则在管道结束时,该引用计数器应达到零,并且将调用内存释放函数: ms_free()。 如果没有发生调用,那么这块内存将不再返回到堆中,即它会“泄漏”。 使用轻量副本的代价是无法轻松确定(与常规副本的情况一样)哪个图形过滤器正在泄漏内存。

由于媒体流开发人员负责查找本机过滤器中的内存泄漏,因此您很可能不需要调试它们。 但有了你的工艺过滤器,你就是自己幸福的蚱蜢,你花在寻找代码漏洞上的时间将取决于你的准确性。 为了减少调试时间,我们需要在开发过滤器时考虑泄漏检测技术。 此外,可能只有当过滤器应用在实际系统中时,泄漏才会显现出来,而“嫌疑人”的数量可能很大,调试时间也有限。

内存泄漏如何表现出来?

可以逻辑地假设在程序输出中 最佳 将显示您的应用程序占用的内存百分比不断增加。

外在表现会是,在某个时刻系统会开始缓慢地响应鼠标移动并缓慢地重绘屏幕。 系统日志也可能会增长,占用硬盘空间。 在这种情况下,您的应用程序将开始表现奇怪,不响应命令,无法打开文件等。

为了检测泄漏的发生,我们将使用内存分析器(以下简称分析器)。 它可能是 瓦尔格朗德 (好的 文章 关于它)或内置到编译器中 GCC 内存消毒器 或其他任何东西。 如果分析仪显示图形过滤器之一发生泄漏,则意味着是时候应用下面描述的方法之一了。

三松法

如上所述,如果存在内存泄漏,分析器将指向从堆请求内存分配的过滤器。 但它不会指出“忘记”返回它的过滤器,而事实上,过滤器才是罪魁祸首。 因此,分析器只能证实我们的恐惧,但不能指出其根源。

要找出图表中“坏”过滤器的位置,您可以将图表减少到分析器仍检测到泄漏的最小节点数,并将有问题的过滤器定位在其余三个松树中。

但是,通过减少图中过滤器的数量,您可能会破坏过滤器与系统其他元素之间的正常交互过程,并且泄漏将不再出现。 在这种情况下,您将必须使用全尺寸图表并使用下面概述的方法。

滑动绝缘子法

为了简化演示,我们将使用由一系列过滤器组成的图表。 她如图所示。

探索 Mediastreamer2 VoIP 引擎。 第 12 部分

一个常规图表,其中除了现成的媒体流过滤器外,还使用了四种不同类型的四个工艺过滤器 F1...F4,您很久以前就制作了这些过滤器,并且对它们的正确性毫不怀疑。 然而,我们假设其中有几个存在内存泄漏。 运行我们的程序来监视分析器,我们从其报告中了解到某个过滤器请求了一定量的内存并且没有将其返回到堆 N 次。 您可以很容易地猜测到会有一个指向 MS_VOID_SOURCE 类型的内部过滤器函数的链接。 他的任务是从堆中取出内存。 其他过滤器必须将其返回那里。 那些。 我们将检测泄漏的事实。

为了确定管道的哪一部分不作为导致内存泄漏,建议引入一个额外的过滤器,该过滤器简单地将消息从输入转移到输出,但同时创建不轻的输入消息的副本,而是一个正常的“重”消息,然后完全删除在入口处收到的消息。 我们将这样的过滤器称为绝缘体。 我们认为,由于过滤器结构简单,因此不会出现泄漏。 还有一个积极的特性 - 如果我们将其添加到图表中的任何位置,这不会以任何方式影响电路的运行。 我们将以带有双电路的圆形形式描述滤波器隔离器。

我们在 voidsource 过滤器之后立即打开隔离器:
探索 Mediastreamer2 VoIP 引擎。 第 12 部分

我们再次使用分析仪运行程序,我们发现这次分析仪将责任归咎于绝缘体。 毕竟,现在是他创建了数据块,然后这些数据块被未知的粗心过滤器(或多个过滤器)丢失了。 下一步是将绝缘体沿链向右移动一个过滤器,然后再次开始分析。 因此,逐步将隔离器向右移动,我们将得到这样的情况:在下一个分析器报告中,“泄漏”内存块的数量将会减少。 这意味着在此步骤中,绝缘体位于问题滤波器之后的链中。 如果只有一个“坏”过滤器,那么泄漏就会完全消失。 因此,我们定位了有问题的过滤器(或多个过滤器之一)。 “修复”过滤器后,我们可以继续将隔离器沿着链向右移动,直到完全消除内存泄漏。

隔离滤波器的实现

隔离器实现看起来就像一个常规过滤器。 头文件:

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

添加评论