Python - C eingebetteter Segmentierungsfehler

9

Ich stehe vor einem ähnlichen Problem wie die Py_initialize / Py_Finalize funktioniert nicht zweimal mit numpy .. Die grundlegende Codierung in C:

%Vor%

Das Programm befindet sich in einer Schleife und es gibt einen seg-Fehler, wenn der Python-Code als eines der importierten Module numpy hat. Wenn ich numpy entferne, funktioniert es gut.

Als vorübergehendes Problem habe ich versucht, Py_Finalize () nicht zu verwenden, aber das führt zu großen Speicherlecks [beobachtet, da die Speicherbelegung von TOP weiter zunimmt]. Und ich habe es versucht, aber den Vorschlag in dem Link, den ich gepostet habe, nicht verstanden. Kann jemand bitte den besten Weg vorschlagen, den Anruf zu beenden, während Importe wie z. B. numpy.

Danke Santosh.

    
user202385 12.02.2013, 22:55
quelle

2 Antworten

3

Ich bin mir nicht ganz sicher, wie Sie die in Py_initialize / Py_Finalize funktioniert nicht zweimal mit numpy . Die gelieferte Lösung ist ziemlich einfach: Rufen Sie Py_Initialize und Py_Finalize nur einmal für jedes Mal auf, wenn Ihr Programm ausgeführt wird. Rufen Sie sie nicht jedes Mal an, wenn Sie die Schleife ausführen.

Ich nehme an, dass Ihr Programm, wenn es startet, einige Initialisierungsbefehle ausführt (die nur einmal ausgeführt werden). Rufen Sie dort Py_Initialize auf. Nennen Sie es nie wieder. Außerdem gehe ich davon aus, dass wenn Ihr Programm beendet wird, es Code hat, um Dinge zu zerlegen, Log-Dateien abzulegen usw. Rufen Sie dort Py_Finalize auf. Py_Initialize und Py_Finalize sollen Ihnen nicht helfen, Speicher im Python-Interpreter zu verwalten. Benutze sie nicht dafür, da sie dein Programm zum Absturz bringen. Verwenden Sie stattdessen Pythons eigene Funktionen, um Objekte zu entfernen, die Sie nicht behalten möchten.

Wenn Sie wirklich jedes Mal eine neue Umgebung erstellen müssen, wenn Sie Ihren Code ausführen, können Sie Py_NewInterpreter verwenden und einen Sub-Interpreter und Py_EndInterpreter erstellen, um diesen Sub-Interpreter später zu zerstören. Sie sind am unteren Rand der Seite Python C API dokumentiert. Dies funktioniert ähnlich wie bei einem neuen Interpreter, außer dass Module nicht jedes Mal neu initialisiert werden, wenn ein Sub-Interpreter gestartet wird.

    
Namey 13.02.2013, 04:13
quelle
2

Ich habe kürzlich ein sehr ähnliches Problem gesehen und eine Workaround entwickelt, die für meine Zwecke funktioniert, also dachte ich, ich würde es hier schreiben, in der Hoffnung, dass es anderen hilft.

Das Problem

Ich arbeite mit einer Postprocessing-Pipeline, für die ich einen eigenen Funktor schreiben kann, um an einigen Daten zu arbeiten, die die Pipeline passieren, und ich wollte Python-Skripte für einige der Operationen verwenden können.

Das Problem ist, dass das einzige, was ich kontrollieren kann, der Funktor selbst ist, der zu Zeiten außerhalb meiner Kontrolle instanziiert und zerstört wird. Ich habe außerdem das Problem, dass, selbst wenn ich Py_Finalize nicht anrufe, die Pipeline manchmal abstürzt, wenn ich ein anderes Dataset durch die Pipeline gebe.

Die Lösung in Kürze

Für diejenigen, die nicht die ganze Geschichte lesen und direkt auf den Punkt kommen wollen, hier ist der Kern meiner Lösung:

Der Grundgedanke meiner Problemumgehung ist, nicht mit der Python-Bibliothek zu verknüpfen, sondern stattdessen dynamisch mit dlopen zu laden und dann alle Adressen der erforderlichen Python-Funktionen mit dlsym zu erhalten. Danach können Sie Py_Initialize() aufrufen, gefolgt von dem, was Sie mit den Python-Funktionen tun möchten, gefolgt von einem Aufruf von Py_Finalize() , sobald Sie fertig sind. Dann kann man einfach die Python-Bibliothek entladen. Das nächste Mal, wenn Sie Python-Funktionen verwenden müssen, wiederholen Sie einfach die obigen Schritte und Bobs Onkel.

Wenn Sie jedoch NumPy zu einem beliebigen Zeitpunkt zwischen Py_Initialize und Py_Finalize importieren, müssen Sie auch nach allen aktuell geladenen Bibliotheken in Ihrem Programm suchen und diese manuell mit dlclose löschen.

Detaillierte Problemumgehung

Laden statt Python zu verknüpfen

Die Hauptidee, wie ich oben erwähnt habe, ist nicht , um mit der Python-Bibliothek zu verlinken. Stattdessen laden wir die Python-Bibliothek dynamisch mit dlopen ():

    #einschließen     ...     void * pHandle = dlopen ("/ Pfad / zu / library / libpython2.7.so", RTLD_NOW | RTLD_GLOBAL);

Der obige Code lädt die gemeinsam genutzte Python-Bibliothek und gibt ein Handle zurück (der Rückgabetyp ist ein unklarer Zeigertyp, also der void* ). Das zweite Argument ( RTLD_NOW | RTLD_GLOBAL ) dient dazu, sicherzustellen, dass die Symbole korrekt in den Anwendungsbereich der aktuellen Anwendung importiert werden.

Sobald wir einen Zeiger auf das Handle der geladenen Bibliothek haben, können wir diese Bibliothek nach den Funktionen durchsuchen, die sie exportiert, indem wir die Funktion dlsym verwenden:

%Vor%

Die Funktion dlsym benötigt zwei Parameter: einen Zeiger auf das Handle der Bibliothek, den wir zuvor erhalten haben, und den Namen der Funktion, nach der wir suchen (in diesem Fall Py_Initialize ). Sobald wir die Adresse der gewünschten Funktion haben, können wir einen Funktionszeiger erstellen und ihn auf diese Adresse initialisieren. Um die Funktion Py_Initialize tatsächlich aufzurufen, würde man einfach schreiben:

%Vor%

Für alle anderen Funktionen, die von der Python C-API zur Verfügung gestellt werden, kann man nur Aufrufe von dlsym hinzufügen und Funktionszeiger auf seinen Rückgabewert initialisieren und dann diese Funktionszeiger anstelle der Python-Funktionen verwenden. Man muss lediglich den Parameter und den Rückgabewert der Python-Funktion kennen, um den richtigen Typ des Funktionszeigers zu erzeugen.

Sobald wir mit den Python-Funktionen fertig sind und Py_Finalize mit einer Prozedur ähnlich der für Py_Initialize aufrufen, kann man die dynamische Python-Bibliothek auf folgende Weise entladen:

%Vor%

Manuelles Entladen von NumPy-Bibliotheken

Leider löst dies nicht die Segmentierungsfehlerprobleme, die beim Importieren von NumPy auftreten. Die Probleme rühren von der Tatsache her, dass NumPy auch einige Bibliotheken lädt, die dlopen (oder etwas Ähnliches) verwenden, und diese werden nicht entladen, wenn Sie Py_Finalize aufrufen. Wenn Sie alle geladenen Bibliotheken in Ihrem Programm auflisten, werden Sie feststellen, dass nach dem Schließen der Python-Umgebung mit Py_Finalize gefolgt von einem Aufruf von dlclose einige NumPy-Bibliotheken im Speicher verbleiben.

Der zweite Teil der Lösung erfordert die Auflistung aller Python-Bibliotheken, die nach dem Aufruf von dlclose(pHandle); im Speicher verbleiben. Nehmen Sie dann für jede dieser Bibliotheken ein Handle zu ihnen und rufen Sie dlclose für sie auf. Danach sollten sie automatisch vom Betriebssystem entladen werden.

Glücklicherweise gibt es Funktionen unter Windows und Linux (Entschuldigung, MacOS, konnte nichts finden, was in Ihrem Fall funktionieren würde ...):  - Linux: dl_iterate_phdr  - Windows: EnumProcessModules in Verbindung mit OpenProcess und GetModuleFileNameEx

Linux

Das ist ziemlich einfach, wenn Sie die Dokumentation über dl_iterate_phdr :

gelesen haben %Vor%

Grundsätzlich durchläuft die Funktion dl_iterate_phdr alle geladenen Bibliotheken (in der umgekehrten Reihenfolge, in der sie geladen wurden), bis entweder der Callback etwas anderes als 0 zurückgibt oder das Ende des Liste. Um die Liste zu speichern, fügt der Callback einfach jedes Element zu einem globalen std::vector hinzu (man sollte natürlich globale Variablen vermeiden und zum Beispiel eine Klasse verwenden).

Windows

Unter Windows wird es etwas komplizierter, aber immer noch überschaubar:

%Vor%

Der Code ist in diesem Fall etwas länger als beim Linux-Fall, aber die Grundidee ist die gleiche: listet alle geladenen Bibliotheken auf und speichert sie in std::vector . Vergiss nicht, dein Programm auch mit dem Psapi.lib ! Zu verknüpfen!

Manuelles Entladen

Jetzt, da wir alle geladenen Bibliotheken auflisten können, müssen Sie nur noch diejenigen finden, die vom Laden von NumPy stammen, ein Handle für sie und dann dlclose für dieses Handle aufrufen. Der folgende Code funktioniert sowohl unter Windows als auch unter Linux, sofern Sie die Bibliothek dlfcn-win32 verwenden.

%Vor%

Schlussworte

Die hier gezeigten Beispiele erfassen die grundlegenden Ideen, die hinter meiner Lösung stehen, können aber sicherlich verbessert werden, um globale Variablen zu vermeiden und die Benutzerfreundlichkeit zu erleichtern (zum Beispiel schrieb ich eine Singleton-Klasse, die die automatische Initialisierung aller Funktionszeiger nach dem Laden behandelt die Python-Bibliothek).

Ich hoffe, dass dies für jemanden in der Zukunft nützlich sein kann.

Referenzen

Tachikoma 02.04.2017 09:10
quelle