Optimierungsvorschlag "HandleTimeout()" in fhem.pl

Begonnen von noansi, 20 Dezember 2017, 21:54:02

Vorheriges Thema - Nächstes Thema

herrmannj

ja, so in der Art.

Man könnte zb einige Variablen (wo auch immer) definieren und an neuralgischen Punkten abfragen:

if (defined($profiler)) {$profiler-> (blub..)}

Das sollte jo nicht so ins Gewicht fallen. Wichtig ist wo die überall stehen und wie ausdifferenziert die sind.

Im Prinzip sollte apptime (und co) nicht mehr die internen Funktionen verbiegen müssen um seinen job zu erledigen. Rückwärts müsste man also fragen "was braucht man generell um verschiedene Fragen zur Performance zu beantworten?" und dort dann diese codepoints hinschreiben.

Wenn keiner was messen will bleibt es bei "is defined?". Nö? dann weiter ..
Das könnte man noch ein wenig kapseln damit sich nicht zwei module gleichzeitig und nicht kooperativ da andocken.

Wenn man es darauf ankommen lassen möchte kann man das auch über conditional compile komplett neutral lösen.

Am Anfang
use constant ('PROFILE', 0);

später
if( PROFILE ){
      $profile->(blub..);
};


Wenn PROFILE auf '0' steht wird der code mit dem if erst garnicht erzeugt. Kann man dann aber nicht mehr über attribute und co steuern.

herrmannj

google empfiehlt da diese Möglichkeit:
use constant PROFILE= $ENV{PROFILE};

start mit
PROFILE=1 fhem.pl ..

Clever!. Wie ging das früher nur ohne google ;)


noansi

Hallo Zusammen,

ich sehe gerade, dass schon die letzte Änderung eventuell erste Nebenwirkungen zeigt https://forum.fhem.de/index.php/topic,75638.msg735154.html#msg735154.

Modbus hat wohl Antwort Timeouts im 2 Sekunden Bereich. Jedoch nach einem Broadcast und zwischen zwei Sendetelgrammen eine Turnaround Delay Zeit im 100-200ms Bereich Stille zu halten, bevor ein Request gesendet wird, wenn ich das im schnellen Überfliegen grob richtig verstanden habe.

Das könnte nun eventuell schief gehen, wenn das Senden schon regelmäßig zu spät ausgeführt würde. Das könnte dadurch ausglöst sein, dass die Timerverarbeitung nun bis zum Startzeitpunkt von HandleTimeout() ausgeführt wird und nicht mehr während der Verarbeitung die weiter laufende Zeit aktualisiert wird, um in dieser Zeit noch fällig werdende zuvor schon registrierte Timer abzuarbeiten.

Gruß, Ansgar.

CoolTux

Zitat von: herrmannj am 22 Dezember 2017, 00:14:45
kann man ja auch optimieren. Der patch oben ist nur POC. Produktiv kann der zu Problemen führen: das aktuelle Verhalten ist das ein "$arg" (in der Praxis Instanz $hash) nur _einen_ timer registrieren kann. Mit der Änderungen auf array könnte ein "$arg" mehrfach registriert werden.

Guten Morgen,

Jörg hierzu eine kleine Frage. Ich kann doch zum jetzigen Zeitpunkt bereits mehrere Timer an einen Hash binden.


InternalTimer( gettimeofday()+4, 'LGTV_WebOS_GetForgroundAppInfo', $hash );

Und das passende Remove

RemoveInternalTimer($hash,'LGTV_WebOS_GetForgroundAppInfo');


Oder ist das was anderes?
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

herrmannj


StefanStrobel

Hallo,

ich habe kein Problem damit, Modbus.pm an ein neues Interface anzupassen.
Bisher greife ich an einer Stelle direkt auf intAt zu, um den Status eines laufenden Timers abzufragen.
Wenn es dafür ein definiertes Interface gäbe, wäre das natürlich eleganter:


   # find internal timeout timer time and calculate remaining timeout
    foreach my $a (keys %intAt) {
        if($intAt{$a}{ARG} eq $arg) {
            $rest = $intAt{$a}{TRIGGERTIME} - $now;
        }
    }


unabhängig davon benötigt Modbus mehrere Timer für z.B.:

  • Abfrage von Werten von einem Modbus-Slave in einem definierten Intervall
  • Timeout beim Warten auf eine Antwort vom abgefragten Modbus-Slave
  • Wartezeit zwischen sequentiellen Abfragen beim Scannen eines Modbus-Slave
  • Abarbeiten der Send-Queue

Ich mache das bisher mit InternalTimer und einem Text-Argument a la "queue:$name" um dann in der aufgerufenen Funktion wieder den Hash zum Device-Namen zu holen.
Wenn das künftig eleganter geht, baue ich auch das gerne um.

Gruss
   Stefan

herrmannj

Du würdest so was wie

$tl = internalTimer_TimeLeft($id) benötigen ? Richtig ?

return undef wenn es den Timer nicht gibt, sonst x.y .. (?)

noansi

#22
Hallo Stefan,

danke für die Änderungsbereitschaft.

Zitatum den Status eines laufenden Timers abzufragen

Warum hast Du den Weg gewählt? Du könntest Dir doch die Timer selbst in einem hash mit Timeout Zeit merken und wenn der Timer ausgeführt wird, oder Du RemoveInternalTimer nutzt, wieder löschen. Damit wäre der Lookup vermutlich deutlich schneller, da Dein $arg der hashkey wäre.

Aber wie sieht es mit der aktuellen Änderung in fhem.pl aus? Kannst Du damit Nebenwirkungen bezüglich Timing feststellen?

Gruß, Ansgar.

herrmannj

ich versuch das (https://forum.fhem.de/index.php/topic,75638.msg735154.html#msg735154) auch gerade nachzuvollziehen, müssen wir ja wieder glattziehen.

Ich verstehe es aber nicht. Der Zeitpunkt von HandleTimeout() bzw dem callback ist doch unverändert ?

noansi

#24
Hallo herrmannj;

ZitatDer Zeitpunkt von HandleTimeout() bzw dem callback ist doch unverändert ?
Nicht ganz.
In der alten Variante wurde in der Schleife über alle timer auch $now aktualisiert, um der Rechenzeit laufender Timerfunktionen Rechnung zu tragen.
Vor der nächsten Timerbehandlung kommt dann auch wieder IO der für weitere Verzögerung sorgen kann.
Außerdem gab es noch die ominöse 0.01 ...
Es gibt also schon Unterschiede.

Es besteht natürlich auch die Möglichkeit eines zufällig aufgetretenen anderen Problems bei dem Beitrag, aber ich kann dazu nicht mal eben ohne irgendwelches Testmaterial nachvollziehen, auf was die Logeinträge wohl alles hinweisen könnten.
Z.B. statt ins Plätzchen in den Abschlusswiderstand gebissen?  ;)

Gruß, Ansgar.

herrmannj

#25
hmmm. Also vorher waren das ja auch eher so plus minus eine Minute wann der callback kam. :) Je nachdem was sonst "so los war".

Mal schauen ob Stefan eine Idee hat.

Nachtrag:
na, wenn der user schreibt dass es mit dem rollback von fhem.pl das beseitigt wird, dann wird es wohl schon was damit zu tun haben. Ich blick nur gerade nicht wo und warum ....

noansi

Hallo herrmannj,

Zitatdem rollback von fhem.pl das beseitigt wird, dann wird es wohl schon was damit zu tun haben

Hatte ich spontan auch gedacht, aber entweder wird Modbus sehr selten genutzt oder Modbus Nutzer sind sehr zurückhaltend mit FHEM updates. Mir fehlt da derzeit mehr Problemfeedback anderer User und zur Analyse ohnehin detailiertere Log Datenbasis.

Gruß, Ansgar.

herrmannj

gab es gerade. Bei einem anderen user (erwartungsgemäß) problemlos. Schaun mer mal :)

rudolfkoenig

Zitatimmi benötigt das wohl bei THZ, um alle Timer seines Moduls auf einmal schnell los zu werden.
Ist noch nicht wirklich eine Erklaerung, ich habe mein Vorschlag mit RemoveInternalTimer aber eingebaut.

ZitatNehme ich mal 20 Nachrichten pro Sekunde (5 rein, 5 raus). Die Netzwerkverbindung ist moderat, jede Nachricht benötigt 2 mal select.
20 x 25ms sind 500ms (auf 1Sekunde).
Ich gehe davon aus, dass du
if($now < $nextat) {
    $selectTimestamp = $now;
    return ($nextat-$now);
  }

uebersehen hast. Ich bin imer noch auf dem Standpunkt, dass HandleTimeout nur bei extrem vielen kurzen Timern relevant ist, und ich verstehe noch nicht, wann sowas benoetigt wird. In meiner Produktiv-Installation spart die ganze Optimierung hier grob geschaetzt pro Stunde 1ms. Und das auch nur, weil zwei Geraete alle 5 Minuten pollen.

ZitatMan könnte die Funktionalität doch direkt im core (fhem.pl), genau hier in den timern, abbilden.
Erstens sind die Timer nur ein Teil (CallFn ist mindestens so wichtig, evtl. auch weitere), zweitens muesste man an diversen Stellen (vor dem Aufruf, nach dem Aufruf,) Hooks anbieten. Ich zoegere noch.

Zitatich sehe gerade, dass schon die letzte Änderung eventuell erste Nebenwirkungen zeigt https://forum.fhem.de/index.php/topic,75638.msg735154.html#msg735154.
Wundere mich wieso. Kann nur an den 0.01 liegen, aber das wuerde ich gerne weghaben.

ZitatBisher greife ich an einer Stelle direkt auf intAt zu, um den Status eines laufenden Timers abzufragen.
Das kann man auch, indem man TRIGGERTIME beim InternalTimer Aufruf merkt. internalTimer_TimeLeft muesste den naechsten Timer zurueckliefern, was wiederum erst nach sortieren bekannt ist, wobei wir bei der Anfangsdiskussion angekommen sind.

noansi

#29
Hallo Rudolf,

hier noch eine kleine Abwandlung von HandleTimeout, um dem ursprünglichen Verhalten näher zu kommen.
Einfach ein bischen mehr mit $minCoverExec in die Zukunft vorbereiten und dann in der Schleife abbrechen, wenn es zu viel war bevor der Timer ausgeführt wird:

#####################################
# Return the time to the next event (or undef if there is none)
# and call each function which was scheduled for this time
sub
HandleTimeout()
{
  return undef if(!$nextat);

  my $minCoverWait = 0.0; # 0.01 by Rudolf, but it should be set only on systems that need it!?! Any way to discover?
                           #      it depends on execution times if timers run a little bit too early
  my $minCoverExec = 2; # look ahead to cope execution time of firing timers
  my $now = gettimeofday();
  my $dtnext = $nextat-$now;
  if($dtnext > $minCoverWait) { # need to cover min delay at least
    $selectTimestamp = $now;
    return $dtnext;
  }

  my ($tim,$fn,$arg); # just for reuse in the loops
  $nextat = 0;
  #############
  # Check the internal list.
  foreach my $i ( sort {$intAt{$a}{TRIGGERTIME} <=> # O(n*log(n)) ore worse,but only for the "firering" timers
                         $intAt{$b}{TRIGGERTIME} }
                    (grep {($intAt{$_}->{TRIGGERTIME}-$now) <= $minCoverExec} # just O(N)
                       keys(%intAt)) ) { # get the timers to execute due to timeout and sort just them ascending by time -> faster as normally about 1 to 5 timers may be timing out and xx-times more are waiting
    $i = "" if(!defined($i)); # Forum #40598
    next if(!$intAt{$i}); # deleted in the loop
    $tim = $intAt{$i}{TRIGGERTIME};
    $fn = $intAt{$i}{FN};
    if(!defined($fn) || !defined($tim)) { # clean up bad entries, but maybe they are a strange signal/flag?
      delete($intAt{$i});
      next;
    }
    if ($tim - gettimeofday() > $minCoverWait) {
      $nextat = $tim; # execution time not reached yet
      last;
    }
    $arg = $intAt{$i}{ARG};
    no strict "refs";
    &{$fn}($arg); # this can delete a timer and can add a timer not covered by the current loops TRIGGERTIME sorted list
    use strict "refs";
    delete($intAt{$i});
  }

  foreach my $i (keys(%intAt)) { # find next timer to trigger, O(N-done) as we need to search all the unsorted rest
    $i = "" if(!defined($i)); # Forum #40598
    next if(!$intAt{$i}); # let the line above still work
    $tim = $intAt{$i}{TRIGGERTIME};
    $nextat = $tim if (   defined($tim)
                       && (   !$nextat # find the next time to trigger
                           || ($nextat > $tim) ) );
  }

  if(%prioQueues) {
    my $nice = minNum(keys %prioQueues);
    my $entry = shift(@{$prioQueues{$nice}});
    delete $prioQueues{$nice} if(!@{$prioQueues{$nice}});
    &{$entry->{fn}}($entry->{arg});
    $nextat = 1 if(%prioQueues);
  }

  $now = gettimeofday(); # if some callbacks took longer
  $selectTimestamp = $now;

  return undef if !$nextat;
 
  $dtnext = $nextat-$now;
  return ($dtnext > $minCoverWait) ? $dtnext : $minCoverWait; # need to cover min delay at least
}


Gruß, Ansgar.