Der Artikelinhalt stammt von meinem .

Letzte Woche , ich habe versprochen, die Frage der Lastbewertung auf dem Ticker und Möglichkeiten zur Bekämpfung übermäßiger Rechenlast im Medienstreaming zu betrachten. Aber ich habe entschieden, dass es logischer ist, die Fragen zur Fehlersuche bei benutzerdefinierten Filtern zu beleuchten, die mit der Datenübertragung verbunden sind, und danach die Fragen zur Leistungsoptimierung zu betrachten.
Fehlersuche bei benutzerdefinierten Filtern
Nachdem wir im vorherigen Artikel den Mechanismus der Datenübertragung im Medienstreaming behandelt haben, ist es sinnvoll, über die darin verborgenen Gefahren zu sprechen. Eine der Besonderheiten des Prinzips „Datenfluss“ besteht darin, dass die Speicherzuweisung aus dem Heap in den Filtern erfolgt, die sich am Ursprung des Datenflusses befinden, während das Freigeben des Speichers mit der Rückgabe in den Heap bereits die Filter durchführen, die sich am Ende des Datenflusses befinden. Darüber hinaus kann das Erstellen neuer Daten und deren Zerstörung irgendwo an Zwischenpunkten stattfinden. Im Allgemeinen führt nicht der Filter, der den Datenblock erstellt hat, die Speicherfreigabe durch.
In Bezug auf eine transparente Überwachung des Speichers wäre es sinnvoll, dass der Filter, der den Eingabeblock erhält, diesen nach der Verarbeitung sofort zerstört und den Speicher freigibt, während er am Ausgang einen neu erstellten Block mit den Ausgabedaten bereitstellt. In diesem Fall könnte eine Speicherleckage im Filter leicht zurückverfolgt werden – wenn der Analysator eine Leckage im Filter entdeckt hat, bedeutet das, dass der nachfolgende Filter die eingehenden Blöcke nicht ordnungsgemäß zerstört und der Fehler dort liegt. Aber in Bezug auf die Aufrechterhaltung einer hohen Leistung ist dieser Ansatz im Umgang mit Datenblöcken nicht produktiv – er führt zu einer hohen Anzahl von Speicherzuweisungs-/freigabeoperationen für Datenblöcke ohne jeglichen nützlichen Output.
Aus diesem Grund verwenden die Filter des Media-Streamers beim Kopieren von Nachrichten Funktionen, die leichte Kopien erstellen, um die Datenverarbeitung nicht zu verlangsamen (wir haben in dem vorherigen Artikel darüber berichtet). Diese Funktionen erstellen lediglich eine neue Instanz des Nachrichtenheaders, die an einen Datenblock der kopierten „alten“ Nachricht „angekoppelt“ wird. Infolgedessen sind zwei Header an einen Block von Daten gebunden, und der Zähler für die Verweise in diesem Datenblock wird inkrementiert. Dies wird jedoch wie zwei Nachrichten erscheinen. Es kann auch mehr als eine Nachricht mit einem solchen „gemeinschaftlichen“ Datenblock geben; zum Beispiel erzeugt der MS_TEE-Filter sofort ein Dutzend solcher leichter Kopien, die auf seinen Ausgängen verteilt werden. Bei ordnungsgemäßem Betrieb aller Filter in der Kette sollte dieser Verweiszähler am Ende der Pipeline null erreichen, und die Funktion zur Freigabe des Speichers wird aufgerufen: ms_free(). Wenn dieser Aufruf nicht erfolgt, bedeutet das, dass dieses Stück Speicher nicht mehr zum Heap zurückkehrt, d.h. es wird „auslaufen“. Der Preis für die Nutzung von leichten Kopien ist der Verlust der Möglichkeit, (wie es bei der Verwendung regulärer Kopien der Fall wäre) leicht festzustellen, in welchem Filter der Graph Speicher ausläuft.
Da die Verantwortung für die Auffindung von Speicherlecks in den „nativ“ implementierten Filtern bei den Entwicklern des Media-Streamers liegt, müssen Sie diese wahrscheinlich nicht debuggen. Aber bei Ihrem eigenen Craft-Filter – Sie sind der Schmied Ihres eigenen Glücks, und wie sorgfältig Sie vorgehen, bestimmt die Zeit, die Sie mit der Suche nach Lecks in Ihrem Code verbringen. Um Ihre Mühen mit dem Debugging zu verkürzen, sollten wir Methoden zur Lokalisierung von Lecks bei der Entwicklung von Filtern betrachten. Zudem kann es sein, dass ein Leck sich erst bei der Anwendung des Filters in einem realen System zeigt, wo die Anzahl der „Verdächtigen“ enorm sein kann und die Zeit zum Debuggen begrenzt ist.
Wie äußert sich ein Speicherleck?
Es ist logisch anzunehmen, dass im Ausgabeprogramm top ein zunehmender Prozentsatz des von Ihrer Anwendung belegten Speichers angezeigt wird.
Das äußere Erscheinungsbild wird darin bestehen, dass das System irgendwann langsam auf die Bewegungen der Maus reagiert und den Bildschirm langsam neu zeichnet. Möglicherweise wird auch das Systemprotokoll wachsen und Speicherplatz auf der Festplatte verbrauchen. In diesem Fall wird Ihre Anwendung seltsam reagieren, keine Befehle annehmen, keine Dateien öffnen usw.
Um den Fakt eines Lecks zu ermitteln, werden wir einen Speicheranalysator verwenden (im Folgenden Analysator genannt). Das kann sein Valgrind (gut darüber) oder im Compiler eingebaut gcc oder etwas anderes. Wenn der Analysator zeigt, dass ein Leck in einem der Filter des Graphen auftritt, bedeutet das, dass es Zeit ist, eine der nachfolgend beschriebenen Methoden anzuwenden.
Drei-Kiefern-Methode
Wie bereits erwähnt, wird der Analysator bei einem Speicherleck auf den Filter hinweisen, der Speicher aus dem Heap angefordert hat. Er wird jedoch nicht auf den Filter hinweisen, der ihn 'vergessen' hat zurückzugeben, was tatsächlich der Schuldige ist. Daher kann der Analysator nur unsere Befürchtungen bestätigen, aber nicht auf deren Ursprung hinweisen.
Um den Standort des 'schlechten' Filters im Graphen zu ermitteln, kann man den Graphen auf die minimale Anzahl von Knoten reduzieren, bei der der Analysator noch ein Leck erkennt, und dann im verbleibenden Drei-Kiefern die problematische Filter lokalisieren.
Es kann jedoch sein, dass das Reduzieren der Anzahl der Filter im Graphen den gewohnten Interaktionsverlauf der Filter mit anderen Elementen Ihres Systems stört und das Leck nicht mehr auftritt. In diesem Fall müssen Sie mit dem vollständigen Graphen arbeiten und den nachfolgend beschriebenen Ansatz verwenden.
Gleitender Isolator-Methode
Zur Vereinfachung verwenden wir einen Graphen, der aus einer einzigen Kette von Filtern besteht. Dieser ist in der Abbildung dargestellt.
![]()
Ein gewöhnlicher Graph, in dem neben den fertigen Filtern des Mediastreamers vier Craft-Filter F1…F4, vier verschiedene Typen, die Sie schon lange erstellt haben und deren Korrektheit Sie nicht in Frage stellen, angewendet werden. Nehmen wir jedoch an, dass einige von ihnen ein Speicherleck aufweisen. Wenn wir unser Programm zur Überwachung des Analysators starten, erfahren wir aus seinem Bericht, dass ein gewisser Filter eine gewisse Menge an Speicher angefordert hat und diese N-anzahl an Malen nicht zurückgegeben hat. Es lässt sich leicht erraten, dass es sich um einen Verweis auf interne Funktionen des Filters vom Typ MS_VOID_SOURCE handelt. Seine Aufgabe ist es, Speicher aus dem Heap zu entnehmen. Zurückgeben sollte ihn ein anderer Filter. Das heißt, wir entdecken das Vorhandensein eines Lecks.
Um zu bestimmen, an welcher Stelle der Pipeline die Inaktivität aufgetreten ist, die zu einem Speicherleck führte, wird vorgeschlagen, einen zusätzlichen Filter einzuführen, der einfach die Nachrichten vom Eingang zum Ausgang weiterleitet, aber dabei eine schwere Kopie der Eingangsnachricht erstellt, und die ankommende Nachricht vollständig entfernt. Wir nennen einen solchen Filter Isolator. Wir nehmen an, dass, da der Filter einfach ist, ein Leak in ihm ausgeschlossen ist. Und eine weitere positive Eigenschaft — wenn wir ihn an irgendeiner Stelle unseres Graphen hinzufügen, hat das keinen Einfluss auf die Funktionsweise des Schemas. Wir stellen den Filter-Isolator als einen Kreis mit doppeltem Rand dar.
Wir aktivieren den Isolator sofort nach dem Filter voidsource:
![]()
Wir starten das Programm erneut mit dem Analysator und sehen, dass der Analysator diesmal die Schuld auf den Isolator schiebt. Denn er ist es, der jetzt Datenblöcke erstellt, die dann von einem unbekannten nachlässigen Filter (oder Filtern) verloren gehen. Im nächsten Schritt verschieben wir den Isolator um einen Filter nach rechts in der Kette und starten die Analyse erneut. So erhalten wir, Schritt für Schritt den Isolator nach rechts bewegend, eine Situation, in der im nächsten Bericht des Analysators die Menge der "ausgelaufenen" Speicherblöcke abnimmt. Das bedeutet, dass sich der Isolator in diesem Schritt direkt nach dem problematischen Filter befindet. Wenn der "schlechte" Filter nur einer war, verschwindet das Leak gänzlich. Somit haben wir den problematischen Filter (oder einen der mehreren) lokalisiert. Nachdem wir den Filter "repariert" haben, können wir den Isolator weiter nach rechts in der Kette bewegen, bis wir die Speicherlecks vollständig besiegt haben.
Implementierung des Filter-Isolators
Die Implementierung des Isolators sieht genauso aus wie ein gewöhnlicher Filter. Überschrift:
/* Файл 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 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 zur Überschreibung von Speicherverwaltungsfunktionen
Für detailliertere Analysen bietet der Mediastreamer die Möglichkeit, die Speicherzugriffs-Funktionen durch Ihre eigenen zu ersetzen, die neben der Hauptfunktion "Wer, wo und warum" erfassen. Drei Funktionen werden überschrieben. Dies geschieht folgendermaßen:
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 hilft in Situationen, in denen der Analyzer die Filter so stark verlangsamt, dass das System, in das unser Schema integriert ist, gestört wird. In einem solchen Fall muss man auf den Analyzer verzichten und die Speicherverwaltungsfunktionen ersetzen.
Wir haben den Aktionsalgorithmus für einen einfachen Graphen ohne Verzweigungen betrachtet. Dieser Ansatz kann jedoch auch auf andere Fälle angewendet werden, natürlich mit Komplikationen, aber die Grundidee bleibt dieselbe.
Im nächsten Artikel betrachten wir die Frage der Lastbewertung auf dem Ticker und Möglichkeiten zur Bekämpfung übermäßiger Rechenlast im Medienstreamer.
Quelle: habr.com
