NEWS
Zykl. getState-Aufrufe vs. eigenes "Cache-Dictionary"
-
@oliverio
Dann zu meiner ganz ursprünglichen Frage:Wie müsste die "MagicFunction" aussehen, um das als zwei-Zeiler funktionierend zu initialisieren?
const idFernseherAn = 'adapter.instanz.bla.blub'; let FernseherAn = MagicFunction(idFernseherAn, FernseherAn); // return der Function ist der initiale getState, und die MagicFunction registriert // die on-Subscription für den Datenpunkt, und "erstellt" die Callback-Function so, // das die übergebene Variable als Referenz aktuell gehalten wird, wenn sich der zugrundeliegende Datenpunkt ändert
Christian
-
@familienvater
Die hätte eine ähnliche Signatur wie die on Funktion
Je nachdem was dir lieber ist kannst du direkt eine callback Funktion mit übergeben oder die gibt ein promise zurück, damit du auf datenänderungen neben deinem caching auch reagieren kannst.
Zusätzlich würde ich auch eine unsubscribe Funktion schreiben, damit man das on für bestimmte datenpunkte wieder abmelden kann -
Ich habe inzwischen eine funktionierende "SmarteVariablen"-TypeScript-Klasse, die wohl das tut, was ich möchte.
Langsam ahne ich aber evtl., was Du mit "doppelt gemoppelt" meintest, verstehe es mangels fehlender/nicht gefundener Dokumentation aber nicht.
Es gibt eine Option im Javascript-Adapter:Nicht alle Zustände beim Start abonnieren
Leider finde ich keine Hinweise darauf, was diese Option bewirkt, aus das das Auswählen syncrone getState() "verhindert", und man dann alles mit await getStateAsync() machen muss, weil eben nicht alle States beim Start abonniert werden, aber getStateAsync ist z.B. in der Adapter-Doku gar nicht vorhanden, das muss man sich im Forum googlen, anderes Thema...
Welche "alle States" abonnieren? Alle States im ganzen ioBroker? Alle in dieser JavaScript-Instanz mit getState angesprochenen Datenpunkte?
I bims total verwirrt...
Christian
-
@familienvater sagte: Welche "alle States" abonnieren? Alle States im ganzen ioBroker?
Ja, alle ioBroker-States und -Objekte werden in den Puffer der Javascript-Instanz geladen.
Mit der asynchronen Version von getState() werden die Daten vom js-controller geholt. -
@paul53 sagte in Zykl. getState-Aufrufe vs. eigenes "Cache-Dictionary":
Ja, alle ioBroker-States und -Objekte werden in den Puffer der Javascript-Instanz geladen.
und wie der Name/die Beschreibung der Option sagt, wahrscheinlich auch entsprechend alle im Java-Adapter abonniert.
Dann ist also ein Zugriff via syncronem getState im javaScript-Adapter ein Zugriff auf einen "State-Cache" im Adapter selber?
Blöde Frage:
Ist es bei einem subscribe() aus einem JS/TS-Sourcefile dann theoretisch egal, ob die "Nicht alle Zustände beim Start abonnieren"-Option ausgewählt ist oder nicht, weil da auf jeden Fall eine Subscription beim js-Controller selber gemacht wird, oder wird das ggf. auch aus dem vom Adapter selbst "vorhandenen" Abonnement vom JS-Adapter wieder an die Subscriptions in den Sourcefiles "multipliziert"?Danke,
Christian -
ja. ist egal.
getState ist die direkte Entsprechung eines Befehls des iobrokers, welcher der javascript-adapter ausführt. wenn alle dps abonniert sind, ist der wert dann schon im javascript adapter zwischengespeichert. wahrscheinlich spart man sich wartezeit, falls der rechner anderweitig schon ausgelastet ist.
auch das abonnieren aller datenpunkte ist ebenfalls so ein caching, welches der javascriptadapter macht.es gab früher mal einen reiter, der wirklich alle ereignisse zu im browser datenpunktänderungen angezeigt hat. das hat aber den browser bzw. nur den tab schnell zur unbenutzbarkeit gebracht. ich denke er wurde deswegen aus iobroker entfernt
hast du den mal performancetests gemacht? da müsste man das messen können, auch wenn es nur wenige millisekunden sind
ich habe mal auf die schnelle einen test gemacht, aber mit und ohne alle abonnieren nicht viel unterschied festgestellt
es könnte natürlich sein, das der getstate befehl von der option nicht abhängt sondern immer bei iobroker nachfragt.
die abfrage hat bei mir immer zwischen 7 und 9 sekunden gedauert, egal ob die option aktiviert war oder nicht.
kann auch sein dass ich da noch ein denkfehler drin habe, aber await wartet ja immer bis der befehl abgeschlossen ist bevor dann der nächste dran ist.async function test() { var start=new Date(); for (var i=0;i<10000;i++) { var x = await getStateAsync("0_userdata.0.test1"); } var end=new Date(); console.log(start); console.log(end); } test();
-
@familienvater sagte: Dann ist also ein Zugriff via syncronem getState im javaScript-Adapter ein Zugriff auf einen "State-Cache" im Adapter selber?
Ja.
@familienvater sagte in Zykl. getState-Aufrufe vs. eigenes "Cache-Dictionary":
Ist es bei einem subscribe() aus einem JS/TS-Sourcefile dann theoretisch egal, ob die "Nicht alle Zustände beim Start abonnieren"-Option ausgewählt ist oder nicht, weil da auf jeden Fall eine Subscription beim js-Controller selber gemacht wird
Ja, Subscriptions verwenden nicht den internen Puffer.
-
Hi,
nein, ich habe noch nicht auf Performance getestet, auch weil mir offensichtlich die Basics noch nicht klar waren, und es damit Garbage In => Garbage Out wäre.
@oliverio sagte in Zykl. getState-Aufrufe vs. eigenes "Cache-Dictionary":
kann auch sein dass ich da noch ein denkfehler drin habe, aber await wartet ja immer bis der befehl abgeschlossen ist bevor dann der nächste dran ist
Denkfehler könnte sein, ich denke, mit dem getStateAsync ist es "egal", welche Option gewählt ist, das wird immer an den JS-Controller durchgestochen, wenn ich es richtig verstanden habe.
Man müsste also wahrscheinlich ein normales syncrones getState im "cachenden" Modus gegen ein asyncrones im nicht-Cachenden Modus vergleichen (und dann sollte das syncrone schneller sein, eigentlich).Und eigentlich müsste man dann schauen, wie viel Zeit die reine Benachrichtigung über die Subscription nach einem "setState" braucht, um eine Aussage zu treffen, ob das Konstrukt mit "abonnierten" Änderungen abbilden auf Variablen CPU-Zyklen-schonender ist als zyklische getStates, und bei welcher Änderungshäufigkeit der BreakEven liegt. Ändert sich der "beobachtete" Datenpunkt häufiger, als er in einer zyklischen Routine abgefragt wird, erzeugt die häufiger triggernde Subscription evtl. mehr Overhead.
Aber ich gehe ja davon aus, das sich z.B. meine "Switches", die ich zyklisch Abfrage, nicht wirklich häufig ändern, sondern fast statisch verhalten.
Aber da kommt mir eine Idee, ich kann ja noch eine Statistk in die Klasse einbauen, die trackt, wie oft der Wert "gelesen" wurde, und wie oft die Subscription getriggert hat, und daraus kann ein "Cache-Hit-Ratio" für den Wert ermitteln, und ggf. Hinweise bei Tageswechsel oder so ins Log schreiben, das dieser Wert evtl. besser jedesmal neu abgefragt wird, und dafür zig unnötige Änderungen über den Trigger eingespart werden könnten.Bleibt auf jeden Fall spannend das Thema,
Christian -
Also, ich habe mal getestet, und zwar so:
async function test_async() { var start=Date.now(); var i=0; for (i=0;i<50000;i++) { var x = (await getStateAsync("0_userdata.0.test1")).val; } var end=Date.now(); console.log(i+".loops with async getState, delta " + (end-start) + "ms"); } function test_sync() { var start=Date.now(); var i=0; for (i=0;i<50000;i++) { var x = getState("0_userdata.0.test1").val; } var end=Date.now(); console.log(i+".loops with sync getState, delta " + (end-start) + "ms"); } test_async(); test_sync();
21:01:42.991 info javascript.0 (320155) script.js.Logik.PerformanceTests: 50000.loops with sync getState, delta 12ms 21:01:42.991 info javascript.0 (320155) script.js.Logik.PerformanceTests: registered 2 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions 21:01:46.748 info javascript.0 (320155) script.js.Logik.PerformanceTests: 50000.loops with async getState, delta 3769ms
Also getState ist "definitv" gecached, und deutlich schneller...
Falls Du Dich wegen der Speed bei mir grundsätzlich wunderst, ioBroker läuft in einer VM mit 2 vCPUs, allerdings ist das ein hochdrehender 6-Core Workstation-XEON mit 3,7GHz (Basis) Takt...Aber zum "Profilen" der Subscription habe ich nicht wirklich eine Idee, evtl. ein PingPong-Messung(Zeitstempel vor setState auf Id1, in der Subscription von Id1 wird Id2 geändert und in Subscription von Id2 wieder Id1, und Id2 macht das nur 1000x oder so und ermittelt dann das Delta, und evtl. könnte man dann mit dem delta/2 sagen, wie lange ein setState mit trigger dauert?
Muss ich mal testen
Und dann könnte es interessant werden, das ganze in einer mit und einer ohne State-Cache-Instanz zu machenChristian
-
Ich bin selbst leicht "erstaunt", das es funktioniert, weil eigentlich darf die gecachte Instanz nur die default 1000 setState pro Minute triggern, aber der callback in den subscriptions läßt sich evtl. nicht auf die zurückverfolgen, oder ist so schnell rum, das noch kein Limiter anspricht?
Und weil ich es in meiner "leeren" Instanz.1 getestet habe, läuft da auch sonst gar nix"use strict"; const loops=5000; let start=0; let end=0; async function test_asyncStartPingPong() { // short delay, to let everything register... await new Promise(r => setTimeout(r, 2000)); console.log("Start "+loops+".loops PingPong now!"); start=Date.now(); // start ping pong await setStateAsync('0_userdata.0.test1',loops); } on({id: '0_userdata.0.test1', change:'any'}, function (obj) { setState('0_userdata.0.test2', obj.state.val-1); }); on({id: '0_userdata.0.test2', change:'any'}, function (obj) { if (obj.state.val>0) setState('0_userdata.0.test1', obj.state.val-1); else { end=Date.now(); console.log(loops+".loops PingPong, received .val "+obj.state.val+" in " + obj.id + ", delta " + (end-start) + "ms"); } }); test_asyncStartPingPong();
Ohne "Caching" geht gar nix, obwohl in meinem Script keine getStates sind, werden welche angemosert, und globale/common Files habe ich keine expliziten in Instanz.1. Und die Werte schwanken "stark", der erste Wert ist direkt nach dem ich die Instanz neu gestartet habe, da ist die aber im Hintergrund evtl. noch dabei, den Cache zu füllen, nach 5 min Wartezeit ist das ganze 10x schneller, dann sogar nochmal etwas schneller, und dann ist es deutlich langsamer, da kann halt auch noch sonstwas auf dem ioBroker los sein, oder auch die anderen VMs auf dem esxi könnten "sonstwas" treiben...
21:59:15.800 info javascript.1 (327919) Start javascript script.js.TestInstanz1.PerformanceTests 21:59:15.813 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: registered 4 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions 21:59:17.813 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: Start 5000.loops PingPong now! 22:00:00.194 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: 5000.loops PingPong, received .val -1 in 0_userdata.0.test2, delta 42380ms 22:05:12.407 info javascript.0 (327870) Stop script script.js.TestInstanz1.PerformanceTests 22:05:12.413 info javascript.1 (327919) Stop script script.js.TestInstanz1.PerformanceTests 22:05:12.416 info javascript.1 (327919) Start javascript script.js.TestInstanz1.PerformanceTests 22:05:12.422 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: registered 4 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions 22:05:14.422 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: Start 5000.loops PingPong now! 22:05:19.332 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: 5000.loops PingPong, received .val -1 in 0_userdata.0.test2, delta 4910ms 22:05:42.881 info javascript.0 (327870) Stop script script.js.TestInstanz1.PerformanceTests 22:05:42.885 info javascript.1 (327919) Stop script script.js.TestInstanz1.PerformanceTests 22:05:42.931 info javascript.1 (327919) Start javascript script.js.TestInstanz1.PerformanceTests 22:05:42.934 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: registered 4 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions 22:05:44.934 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: Start 5000.loops PingPong now! 22:05:49.415 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: 5000.loops PingPong, received .val -1 in 0_userdata.0.test2, delta 4481ms 22:06:04.647 info javascript.1 (327919) Stop script script.js.TestInstanz1.PerformanceTests 22:06:04.646 info javascript.0 (327870) Stop script script.js.TestInstanz1.PerformanceTests 22:06:04.650 info javascript.1 (327919) Start javascript script.js.TestInstanz1.PerformanceTests 22:06:04.653 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: registered 4 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions 22:06:06.653 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: Start 5000.loops PingPong now! 22:06:17.941 info javascript.1 (327919) script.js.TestInstanz1.PerformanceTests: 5000.loops PingPong, received .val -1 in 0_userdata.0.test2, delta 11287ms
Ich danke, man kann so eine ungefähre Aussage zur RoundTrip-Zeit machen, im Optimalfall ist es ca. 1 ms, die es vom setState bis zur Ankunft im subscribe-Callback braucht (auf meinem 3,7GHz-Xeon).
Christian