Hallo zusammen,
Kurzfassung: hier ist ein EXPERIMENTELLES neues Feature für das Wiregate (oder Communitygate, whatever): Vorkompilieren von Plugins mit dem Zweck
* der schnelleren Ausführung
* und weniger Memleaks.
Es handelt sich um einen Patch für den Wiregate-Daemon. Die Geschichts sollte vollständig abwärtskompatibel zu heutigen Plugins sein.
Langfassung:
Hintergrund der Entwicklung sind die leidigen Memleaks bei Plugins, die auf die Dauer immer wieder zum Neustart führen, außerdem die Tatsache, dass gewisse "zeitkritische" Plugins wie Logikprozessor, Russound_RIO usw. manchmal recht träge reagieren.
Die Ursache für beides vermutete ich in den zahlreichen "eval"-Aufrufen, die im wiregated-Daemon geschehen:
1. Jedes Plugin wird bei jedem Aufruf per eval neu geparset und dann ausgeführt.
2. Durch die Programmierweise der meisten - auch meiner - Plugins, geschieht sogar noch ein verschachteltes zweites "eval" innerhalb des Plugins für die Konfigurationsdatei.
Dem wollte ich abhelfen und habe überlegt, wie man das ständige Parsen reduzieren kann. Natürlich muss die Lösung abwärtskompatibel sein, damit heutige Plugins noch laufen. Mein Vorschlag: ausgewählte Plugins, die häufig laufen, werden vorkompiliert und in diesem Zustand gespeichert.
Konkret funktioniert das so: falls ein Plugin irgendwo das Schluesselwort COMPILE_PLUGIN enthält - etwa in einer Kommentarzeile - dann versucht der wiregated dieses Plugin in eine namenlose Subroutine (closure) zu verwandeln. Gelingt dieses nicht fehlerfrei, oder ist das Schluesselwort COMPILE_PLUGIN nicht vorhanden, so wird das Plugin wie bisher per "eval" interpretiert, ohne es zu kompilieren. Gelingt aber der Kompiliervorgang, wird der Plugincode vorkompiliert gecachet und muss ab sofort nur noch aufgerufen werden.
Die praktische Umsetzung funktioniert so, dass um das Plugin herum der Text "sub { .... }" gepackt wird, das Ergebnis durch eval geschickt wird und dessen Ergebnis wiederum in einem Hash %plugin_code für die spätere Ausführung gespeichert wird.
Kein Licht ohne Schatten: wenn man Plugins "kompilierbar" schreiben will, muss man eine Regel beachten, die damit zusammenhängt, dass das Plugin am Ende eine Subroutine wird und Perl benannte Subroutinen innerhalb von anderen Subroutinen eigentlich nicht kennt.
Die einzige konkrete Auswirkung dieses Umstandes, die mir einfällt: Subroutinen innerhalb von kompilierten Plugins können NICHT mehr auf Variablen im Haupttext des Plugins zugreifen. (Sie können aber wie bisher auf ihre eigenen lokalen ("my") Variablen zugreifen und auch auf die Variablen des Daemons, zB %msg und $fh, oder auch %plugin_subscribe).
Beispiel
(sowas ist übrigens sowieso schlechter Programmierstil, aber über Stil zu diskutieren ist un-PERL-ig).
Workaround: Um das zu umgehen, muss man die Variablen explizit übergeben:
Ein anderer Workaround wäre, die Sub ebenfalls als Closure zu definieren, denn die vorgenannte Komplikation besteht nur bei benannten Subroutinen:
(wer mehr wissen will -> man perlref und nach Function Templates suchen).
Zwei weitere, sehr einfache und vom Vorgenannten getrennte Ideen zur Wiregated-Verbesserung habe ich auch umgesetzt:
1. Um Plugin-Konfigurationen zwischenzuspeichern, sollte neben %plugin_info (das per tie an eine Datenbank angebunden ist, damit persistent, aber langsam und auf skalare Daten beschränkt) noch ein %plugin_cache existieren. Daten, die hier abgelegt werden, sind nach einem Neustart des wiregated weg, dafür sind sie zwischendurch schnell verfügbar und bei geschickter Plugin-Programmierung kann man so auch auf die eval-Aufrufe für die Konfigurationsdatei verzichten (bis auf einmal in der allerersten Initialisierung des Plugins). Ich schlage vor, ein solches %plugin_cache innerhalb Plugins so zu benutzen:
Wenn man das bewusst einsetzt, kann man auch das Einlesen von Konfigurationen stark reduzieren. Ich habe natürlich auch den Logikprozessor kompilierbar gemacht (die conf-Dateien bleiben dabei völlig unverändert) und speichere die Logiken nun in %plugin_cache ab -> deutlicher Geschwindigkeitszuwachs, und auch die Memleaks sehe ich seitdem subjektiv deutlich reduziert. Den kompilierbaren Logikprozessor stelle ich aber erstmal nicht ins SVN sondern poste ihn hier, weil er von %plugin_cache abhängt und ich erst warten will, ob der Patch angenommen wird.
2. knx_write sieht aktuell die Möglichkeit vor, write- und response-Requests abzusetzen. (Für response muss man nur den vierten Parameter beim Aufruf auf 1 setzen). Man braucht aber auch die Möglichkeit, ein non-blocking-Read-telegramm abzusetzen, also die Leseanfrage zu schicken, ohne dass man auf die Antwort überhaupt wartet. Anwendungsbeispiel: man möchte regelmäßig einen Wert von einem Aktor abfragen, der das zyklische Senden dieses Wertes nicht unterstützt. Dann helfen solche non-blocking Read-Requests.
Kurzfassung: hier ist ein EXPERIMENTELLES neues Feature für das Wiregate (oder Communitygate, whatever): Vorkompilieren von Plugins mit dem Zweck
* der schnelleren Ausführung
* und weniger Memleaks.
Es handelt sich um einen Patch für den Wiregate-Daemon. Die Geschichts sollte vollständig abwärtskompatibel zu heutigen Plugins sein.
Langfassung:
Hintergrund der Entwicklung sind die leidigen Memleaks bei Plugins, die auf die Dauer immer wieder zum Neustart führen, außerdem die Tatsache, dass gewisse "zeitkritische" Plugins wie Logikprozessor, Russound_RIO usw. manchmal recht träge reagieren.
Die Ursache für beides vermutete ich in den zahlreichen "eval"-Aufrufen, die im wiregated-Daemon geschehen:
1. Jedes Plugin wird bei jedem Aufruf per eval neu geparset und dann ausgeführt.
2. Durch die Programmierweise der meisten - auch meiner - Plugins, geschieht sogar noch ein verschachteltes zweites "eval" innerhalb des Plugins für die Konfigurationsdatei.
Dem wollte ich abhelfen und habe überlegt, wie man das ständige Parsen reduzieren kann. Natürlich muss die Lösung abwärtskompatibel sein, damit heutige Plugins noch laufen. Mein Vorschlag: ausgewählte Plugins, die häufig laufen, werden vorkompiliert und in diesem Zustand gespeichert.
Konkret funktioniert das so: falls ein Plugin irgendwo das Schluesselwort COMPILE_PLUGIN enthält - etwa in einer Kommentarzeile - dann versucht der wiregated dieses Plugin in eine namenlose Subroutine (closure) zu verwandeln. Gelingt dieses nicht fehlerfrei, oder ist das Schluesselwort COMPILE_PLUGIN nicht vorhanden, so wird das Plugin wie bisher per "eval" interpretiert, ohne es zu kompilieren. Gelingt aber der Kompiliervorgang, wird der Plugincode vorkompiliert gecachet und muss ab sofort nur noch aufgerufen werden.
Die praktische Umsetzung funktioniert so, dass um das Plugin herum der Text "sub { .... }" gepackt wird, das Ergebnis durch eval geschickt wird und dessen Ergebnis wiederum in einem Hash %plugin_code für die spätere Ausführung gespeichert wird.
Kein Licht ohne Schatten: wenn man Plugins "kompilierbar" schreiben will, muss man eine Regel beachten, die damit zusammenhängt, dass das Plugin am Ende eine Subroutine wird und Perl benannte Subroutinen innerhalb von anderen Subroutinen eigentlich nicht kennt.
Die einzige konkrete Auswirkung dieses Umstandes, die mir einfällt: Subroutinen innerhalb von kompilierten Plugins können NICHT mehr auf Variablen im Haupttext des Plugins zugreifen. (Sie können aber wie bisher auf ihre eigenen lokalen ("my") Variablen zugreifen und auch auf die Variablen des Daemons, zB %msg und $fh, oder auch %plugin_subscribe).
Beispiel
Code:
my $a=1; my $b=3; sub MySub; # Deklaration plugin_log($plugname, "Test=".MySub()); # Aufruf sub MySub { return $a+$b; } # geht NICHT, Compilerfehler
Workaround: Um das zu umgehen, muss man die Variablen explizit übergeben:
Code:
my $a=1; my $b=3; sub MySub; # Deklaration plugin_log($plugname, "Test=".MySub($a,$b)); # Aufruf mit Übergabe sub MySub { my ($a,$b)=@_; return $a+$b; } # korrekt
Code:
my $a=1; my $b=3; local *MySub = sub { return $a+$b}; # Definition jetzt VOR dem ersten Aufruf! plugin_log($plugname, "Test=".MySub()); # geht
Zwei weitere, sehr einfache und vom Vorgenannten getrennte Ideen zur Wiregated-Verbesserung habe ich auch umgesetzt:
1. Um Plugin-Konfigurationen zwischenzuspeichern, sollte neben %plugin_info (das per tie an eine Datenbank angebunden ist, damit persistent, aber langsam und auf skalare Daten beschränkt) noch ein %plugin_cache existieren. Daten, die hier abgelegt werden, sind nach einem Neustart des wiregated weg, dafür sind sie zwischendurch schnell verfügbar und bei geschickter Plugin-Programmierung kann man so auch auf die eval-Aufrufe für die Konfigurationsdatei verzichten (bis auf einmal in der allerersten Initialisierung des Plugins). Ich schlage vor, ein solches %plugin_cache innerhalb Plugins so zu benutzen:
Code:
$plugin_cache{$plugname}{simpel}=4; # ein simples Beispiel $plugin_cache{$plugname}{komplex}=\%my_big_hash; # hier wird ein ganzes Hash zwischengespeichert
2. knx_write sieht aktuell die Möglichkeit vor, write- und response-Requests abzusetzen. (Für response muss man nur den vierten Parameter beim Aufruf auf 1 setzen). Man braucht aber auch die Möglichkeit, ein non-blocking-Read-telegramm abzusetzen, also die Leseanfrage zu schicken, ohne dass man auf die Antwort überhaupt wartet. Anwendungsbeispiel: man möchte regelmäßig einen Wert von einem Aktor abfragen, der das zyklische Senden dieses Wertes nicht unterstützt. Dann helfen solche non-blocking Read-Requests.
Kommentar