Der Inhalt dieses Artikels stammt von meinem .

Im letzten , habe ich versprochen, die Bewertung der Last auf dem Ticker und Möglichkeiten zur Bekämpfung übermäßiger Rechenlast im Mediastreamer zu behandeln. Ich habe jedoch beschlossen, dass es sinnvoller ist, die Debuginformationen zu den Craft-Filtern, die mit der Datenübertragung verbunden sind, zu beleuchten, bevor ich die Leistungsoptimierung bespreche.
Debuginformationen zu Craft-Filtern
Nachdem wir im vorherigen Artikel den Mechanismus der Datenübertragung im Mediastreamer behandelt haben, ist es sinnvoll, über die darin verborgenen Gefahren zu sprechen. Eine der Besonderheiten des "Data Flow"-Prinzips besteht darin, dass der Speicher aus dem Heap in den Filtern, die am Ursprung des Datenstroms liegen, zugeteilt wird, während die Rückgabe des Speichers in den Heap von den Filtern am Ende des Datenflusses erfolgt. Darüber hinaus kann die Erstellung neuer Daten und deren Zerstörung an zwischengeschalteten Punkten stattfinden. Im Allgemeinen führt nicht der Filter, der den Datenblock erstellt hat, die Speicherfreigabe durch.
In Bezug auf transparentes Monitoring des Speichers wäre es sinnvoll, dass der Filter den eingehenden Block nach der Verarbeitung sofort vernichtet und den neu erzeugten Block mit den Ausgabedaten ausgibt. Auf diese Weise könnte ein Speicherleck im Filter leicht zurückverfolgt werden — wenn der Analyzer ein Leck im Filter entdeckt, bedeutet das, dass der nachfolgende Filter die eingehenden Blöcke nicht ordnungsgemäß zerstört und ein Fehler dort vorliegt. Allerdings ist dieser Ansatz zur Handhabung von Datenblöcken aus der Sicht der Leistung nicht produktiv — er führt zu einer hohen Anzahl von Allokationen und Freigaben von Speicher für Datenblöcke ohne nennenswerten Nutzen.
Aus diesem Grund verwenden die Mediastreamer-Filter zum Kopieren von Nachrichten Funktionen, die leichte Kopien erstellen, um die Datenverarbeitung nicht zu verlangsamen (wir haben in unserem letzten Artikel darüber gesprochen). Diese Funktionen erstellen lediglich eine neue Instanz des Nachrichten-Headers, wobei sie einen Datenblock vom zu kopierenden "alten" Nachricht anhängen. Dadurch sind zwei Header an einen Datenblock gebunden, und der Verweiszähler im Datenblock wird inkrementiert. Es wird jedoch so erscheinen, als gäbe es zwei Nachrichten. Es können sogar noch mehr Nachrichten mit einem solchen "geteilten" Datenblock existieren; beispielsweise erzeugt der MS_TEE-Filter sofort ein Dutzend solcher leichter Kopien und verteilt sie auf seine Ausgänge. Bei korrekter Funktion aller Filter in der Kette sollte dieser Verweiszähler am Ende der Pipeline null erreichen, und die Speicherfreigabefunktion wird aufgerufen. ms_free(). Wenn keine Rückrufe erfolgen, bedeutet das, dass dieser Speicherblock nicht mehr an den Heap zurückgegeben wird, das heißt, er "verliert sich". Der Preis für die Verwendung leichter Kopien besteht darin, dass die Möglichkeit verloren geht, (wie im Fall der Verwendung normaler Kopien) leicht festzustellen, in welchem Filter der Graf Speicher verloren geht.
Da die Verantwortung für die Suche nach Speicherlecks in "eigenen" Filtern bei den Entwicklern des Media-Streamers liegt, werden Sie sie wahrscheinlich nicht debuggen müssen. Bei Ihrem benutzerdefinierten Filter sind Sie jedoch der Schmied Ihres eigenen Glücks, und Ihre Sorgfalt wird darüber entscheiden, wie viel Zeit Sie mit der Suche nach Lecks in Ihrem Code verbringen. Um Ihre Mühen beim Debugging zu verkürzen, sollten wir die Techniken zur Lokalisierung von Lecks bei der Entwicklung von Filtern betrachten. Es kann auch vorkommen, dass ein Leck sich erst dann bemerkbar macht, wenn der Filter in einem realen System angewendet wird, in dem die Anzahl der "Verdächtigen" enorm sein könnte, während die Zeit für das Debugging begrenzt ist.
Wie äußert sich ein Speicherleck?
Es ist naheliegend anzunehmen, dass im Output des Programms top ein ansteigender Prozentsatz des von Ihrer Anwendung verwendeten Speichers angezeigt wird.
Das äußere Erscheinungsbild wird sich darin äußern, dass das System irgendwann träge auf Bewegungen der Maus reagiert und den Bildschirm langsam neu zeichnet. Es kann auch sein, dass das Systemprotokoll wächst und Speicherplatz auf der Festplatte beansprucht. In dieser Zeit wird Ihre Anwendung seltsam reagieren, keine Befehle mehr annehmen oder Dateien nicht öffnen können.
Um den Verdacht auf einen Speicherleck zu überprüfen, werden wir einen Speicheranalysator (im Folgenden als Analysator bezeichnet) verwenden. Dies kann sein Valgrind (ein guter darüber) oder der im Compiler integrierte gcc oder etwas anderes. Wenn der Analysator zeigt, dass ein Leck in einem der Filter des Graphen auftritt, bedeutet das, dass es an der Zeit ist, eine der unten beschriebenen Methoden anzuwenden.
Die Methode der drei Kiefern
Wie bereits erwähnt, wird der Analysator bei einem Speicherleck auf den Filter hinweisen, der die Speicherausweisung von der Heap angefordert hat. Er wird jedoch nicht auf den Filter hinweisen, der "vergessen" hat, ihn zurückzugeben, welcher tatsächlich der Schuldige ist. Somit kann der Analysator lediglich unsere Befürchtungen bestätigen, aber nicht auf die Wurzel des Problems hinweisen.
Um den Standort des "problematischen" Filters im Graphen zu bestimmen, kann man den Graphen auf die minimal erforderliche Anzahl von Knoten reduzieren, bei der der Analysator weiterhin die Leckage entdeckt, und dabei in den verbleibenden drei Knoten den problematischen Filter lokalisieren.
Es kann jedoch passieren, dass die Reduzierung der Filteranzahl im Graphen den normalen Interaktionsfluss der Filter mit anderen Elementen Ihres Systems stört und die Leckage nicht mehr auftritt. In diesem Fall müssen Sie mit dem vollständigen Graphen arbeiten und die nachstehende Methode anwenden.
Das Gleitrückhaltesystem
Zur Vereinfachung verwenden wir einen Graphen, der aus einer Kette von Filtern besteht. Dieser ist in der Abbildung dargestellt.
![]()
Grafik, in dem neben den vorhandenen Filtern des Medienstreamers vier Craft-Filter F1…F4 angewendet werden, die Sie lange erstellt haben und deren Korrektheit Sie nicht in Frage stellen. Angenommen, einige davon haben jedoch ein Speicherleck. Wenn wir unser Überwachungsprogramm des Analysetools ausführen, erfahren wir aus seinem Bericht, dass ein gewisser Filter eine bestimmte Menge an Speicher angefordert hat und diesen N-mal nicht an den Heap zurückgegeben hat. Es ist leicht zu erraten, dass auf interne Funktionen des Filters vom Typ MS_VOID_SOURCE verwiesen wird. Seine Aufgabe ist es, Speicher aus dem Heap zu holen. Zurückgeben sollte ihn ein anderer Filter. Das bedeutet, wir haben ein Speicherleck entdeckt.
Um zu bestimmen, an welcher Stelle im Förderband die Inaktivität, die zu einem Speicherleck geführt hat, aufgetreten ist, empfiehlt es sich, einen zusätzlichen Filter einzuführen. Dieser Filter leitet die Nachrichten einfach vom Eingang zum Ausgang weiter, erstellt dabei jedoch eine nicht leichte, sondern eine "schwere" Kopie der Eingangsnachricht und entfernt dann die eingehende Nachricht vollständig. Wir nennen diesen Filter Isolator. Wir gehen davon aus, dass das einfache Design des Filters ein Leck ausschließt. Ein weiteres positives Merkmal ist, dass die Hinzufügung an irgendeiner Stelle in unserem Diagramm keinen Einfluss auf die Funktionsweise des Systems hat. Wir stellen den Filter-Isolator als einen Kreis mit doppeltem Rand dar.
Wir aktivieren den Isolator sofort nach dem Filter voidsource:
![]()
Wir starten erneut das Programm mit dem Analyzer und sehen, dass es diesmal die Verantwortung dem Isolator zuschreibt. Denn er erzeugt jetzt die Datenblöcke, die dann von einem unbekannten, nachlässigen Filter (oder Filtern) verloren gehen. Im nächsten Schritt verschieben wir den Isolator in der Kette um einen Filter nach rechts und starten die Analyse erneut. So, Schritt für Schritt, bewegen wir den Isolator nach rechts, wodurch wir eine Situation erreichen, in der im nächsten Bericht des Analyzers die Anzahl der "ausgelaufenen" Speicherblöcke verringert wird. Das bedeutet, dass der Isolator in dieser Phase direkt nach dem problematischen Filter positioniert wurde. Wenn nur ein "schlechter" Filter vorhanden war, wird die Leckage möglicherweise sogar ganz verschwinden. Auf diese Weise haben wir den problematischen Filter (oder einen von mehreren) lokalisiert. Nachdem wir den Filter "repariert" haben, können wir den Isolator weiterhin nach rechts in der Kette verschieben, um die vollständige Kontrolle über die Speicherlecks zu erreichen.
Implementierung des Isolator-Filters
Die Implementierung des Isolators sieht ebenso aus wie ein normaler Filter. Header-Datei:
/* Файл 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
Der eigentliche Filter:
/* Файл 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)
Methode zum Überschreiben der Speicherverwaltungsfunktionen
Für detailliertere Analysen bietet der Mediastreamer die Möglichkeit, die Zugriffsfunktionen auf den Speicher durch eigene zu ersetzen, die neben der Hauptfunktion auch aufzeichnen, "Wer, wohin und warum". Es werden drei Funktionen ersetzt. Dies geschieht wie folgt:
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);Diese Möglichkeit ist hilfreich in Situationen, in denen der Analyzer die Filter so stark verlangsamt, dass das System, in das unser Schema integriert ist, nicht mehr funktioniert. In solch einem Fall muss auf den Analyzer verzichtet und stattdessen die Funktionen zur Speicherverwaltung umgestellt werden.
Wir haben den Handlungsalgorithmus für einen einfachen Graphen ohne Verzweigungen betrachtet. Dieser Ansatz kann jedoch auch auf andere Fälle angewendet werden, natürlich mit einer Komplexitätssteigerung, aber die Idee bleibt dieselbe.
Im nächsten Artikel werden wir die Lastbewertung auf dem Ticker und Möglichkeiten zur Bekämpfung einer übermäßigen Rechenlast im Mediastreamer behandeln.
Quelle: habr.com
