Cookies op Tweakers

Tweakers maakt gebruik van cookies, onder andere om de website te analyseren, het gebruiksgemak te vergroten en advertenties te tonen. Door gebruik te maken van deze website, of door op 'Ga verder' te klikken, geef je toestemming voor het gebruik van cookies. Wil je meer informatie over cookies en hoe ze worden gebruikt, bekijk dan ons cookiebeleid.

Meer informatie

Vraag


  • m.van.kan
  • Registratie: augustus 2015
  • Laatst online: 11-06 16:33
Sinds enige tijd ben ik aan de slag met programmeren in Python. Hierbij wil ik onder andere gebruik maken van de Telegram API, waarvoor gebruik gemaakt wordt van de requests module van Python.

Wat echter opvalt is dat het laden van deze module (import requests) erg lang duurt.

Python:
1
2
3
4
5
$ time python3 -c 'import requests'

real   0m3,638s
user   0m3,638s
sys    0m3,638s

Het geheel draait op een Raspberry Pi 3B+ met o.a.:
requests 2.20.1
request-oauthlib 1.0.0
pyOpenSSL 18.0.0
cryptography 2.4.1
cffi 1.11.5
chardet 3.0.4
urllib3 1.24.1

Ik heb al een analyse gedaan door gebruikt te maken van profimp. Deze laat zien dat de tijd met name 'verloren' gaat in:

code:
1
2
3
import urllib3                          1032ms
from urllib3.contrib import pyopenssl   998ms
from import utils                       392ms

Concreet wil ik graag een alarmsysteem opzetten met behulp van Domoticz. Om de code overzichtelijk en uniform te houden wil ik de afhandeling van sensoren (magneetcontacten / bewegingsmelders) niet afhandelen in Domoticz maar in een stukje Python code. Met name omdat ik voor dezelfde sensoren in diverse scenario's (arm home / arm away) andere inlooptijden / acties wil koppelen). Via dzVents in Domoticz wordt een generiek script aangroepen met de naam van de sensor die geactiveerd is. Binnen dit Python script moet de security-status van Domoticz opgevraagd worden (via de requests-module), en eventueel ook een Telegram-bericht verstuurd worden (ook weer via de requests-module).

De Domoticz status zou ik eventueel nog als parameter kunnen meegeven aan de aanroep, en door middel van 'lazy' loading de 'import requests' pas uitvoeren vlak voordat de Telegram-api wordt aangesproken, echter maakt dit het geheel naar mijn idee niet doorzichtiger en geniet daardoor niet de voorkeur.

Andere optie zou een het maken van een achtergrondservice zijn. Ik heb al een achtergrondservice ontwikkeld en draaien m.b.t. aanwezigheidsdetectie met behulp van Unifi en Bluetooth. Hier wordt ook gebruik gemaakt van de requests-module, maar deze wordt slechts één keer geladen waardoor deze timeout niet relevant is. Ik vermoed echter dat ook deze niet voldoende snelheid (single loop) 'haalt' om alle events vanuit Domoticz te ontvangen. Deze zou dan immers in een loop de Domoticz-status van de sensoren moeten 'pollen'. Een deur kan dusdanig snel open en dicht dat deze mutatie wellicht over het hoofd gezien wordt. Overal zijn uiteraard oplossingen voor, maar de code moet ook leesbaar blijven, zonder veel workarounds.

Iemand suggesties?

Alle reacties


  • ValHallASW
  • Registratie: februari 2003
  • Niet online
Allereerst: hoe erg is het dat het eventjes duurt voordat de code draait? Hoe vaak wordt het script aangeroepen? Als Domoticz netjes twee keer je code opstart (en wellicht zelfs er voor zorgt dat ze niet tegelijk draaien?) dan is het wellicht wat traag, maar nog steeds goed genoeg.

Als je voor de service-methode gaat dan zou ik niet vanuit de service Domoticz pollen (tenzij er een API is die daar specifiek voor geschikt is), maar Domoticz change events laten sturen. Dan kan je doen door er iets van Redis tussen te hangen, of door je service bv. een webserver te laten exposen die je met een curl request aanspreekt (op dezelfde manier waarop je nu direct Python start).

Als je de huidige structuur handhaaft dan zijn er een aantal opties waar je aan kunt denken:

1) Check dat er precompiled .pyc files gebruikt worden. Als je een centraal geinstalleerde Python hebt dan zou het kunnen dat die a) niet aanwezig zijn en b) niet door Python kunnen worden weggeschreven omdat je geen schrijfrechten hebt.

2) Mocht je nog geen virtualenv gebruiken: probeer of een virtualenv met alleen de minimale hoeveelheid pakketten geinstalleerd sneller is. Als er ooit pakketten met easy_install zijn geinstalleerd dan zou dat het eea trager kunnen maken door hoe imports destijds geregistreerd werden.

3) Code-wise is de makkelijkste optie wat je zelf al aangaf: lazy loading. Ik zou dan gewoon de import statements verplaatsen bovenaan de functies waar je ze gebruikt. Python zorgt er zelf voor dat een tweede import call gecached is.

4) Je kunt overwegen een virtualenv op een ramdisk te plaatsen. 't is een beetje van dik hout zaagt men planken, maar als 't werkt ;-)

5) Met https://criu.org/Main_Page kan je een process dump maken en die vervolgens herladen. Dat is waarschijnlijk snel, maar levert wel een hoop complexiteit op.

  • m.van.kan
  • Registratie: augustus 2015
  • Laatst online: 11-06 16:33
ValHallASW schreef op zaterdag 17 november 2018 @ 14:57:
Allereerst: hoe erg is het dat het eventjes duurt voordat de code draait? Hoe vaak wordt het script aangeroepen? Als Domoticz netjes twee keer je code opstart (en wellicht zelfs er voor zorgt dat ze niet tegelijk draaien?) dan is het wellicht wat traag, maar nog steeds goed genoeg.
De code zal bij iedere melding van pir of magneetcontact getriggerd worden. Vanuit deze code zal moeten gecontroleerd worden wat de alarm-status in Domoticz is (arm home / arm away / disarmed). Indien armed dan wordt er afhankelijk van de sensor en status (home / away) een inloopvertraging geactiveerd. Tijdens deze inloopvertraging wil ik wellicht nog een puls-signaal afgeven (lamp / buzzer), dus feitelijk kom ik door deze functionaliteit al niet meer onder een achtergrondservice uit.
Als je voor de service-methode gaat dan zou ik niet vanuit de service Domoticz pollen (tenzij er een API is die daar specifiek voor geschikt is), maar Domoticz change events laten sturen. Dan kan je doen door er iets van Redis tussen te hangen, of door je service bv. een webserver te laten exposen die je met een curl request aanspreekt (op dezelfde manier waarop je nu direct Python start).
Wat zou het bezwaar hierin zijn om Domoticz via de api te pollen? Enige bottleneck hierin zou mijns inziens de algehele performance kunnen zijn. Uiteraard is 'event driven' beter als pollen, echter moet er hiervoor weer iets 'tussen'. Idealiter zou Domoticz mijn 'main controller' zijn, echter vanwege mijn specifieke eisen kom ik hier niet mee gedraaid en moet ik dus uitwijken naar iets anders. Python in dit geval omdat ik daarvoor al code heb liggen die een aantal gerelateerde zaken regelt (o.a. Domoticz api / Telegram api / bluetooth / Unifi api). Ik wil idealiter niet teveel afhankelijkheden creëren.
Als je de huidige structuur handhaaft dan zijn er een aantal opties waar je aan kunt denken:

1) Check dat er precompiled .pyc files gebruikt worden. Als je een centraal geinstalleerde Python hebt dan zou het kunnen dat die a) niet aanwezig zijn en b) niet door Python kunnen worden weggeschreven omdat je geen schrijfrechten hebt.
Ik zie dat inderdaad niet iedere .py file een equivalente .pyc heeft. Dit ga ik eens nader bekijken, bedankt voor de tip!
2) Mocht je nog geen virtualenv gebruiken: probeer of een virtualenv met alleen de minimale hoeveelheid pakketten geinstalleerd sneller is. Als er ooit pakketten met easy_install zijn geinstalleerd dan zou dat het eea trager kunnen maken door hoe imports destijds geregistreerd werden.
Installatie van aanvullende modules zijn veelal via pip(3) uitgevoerd. Ik heb onlangs gelezen over virtuelenv en ga eens nader bekijken hoe ik dit kan toepassen. Als ik in de 'algemene' Python library kijk naar de geïnstalleerde modules zijn dat er veel meer dan daadwerkelijk nodig voor mijn eigen scripts, maar ik ben wat huiverig om 'zomaar' dingen te verwijderen, aangezien ik niet 100% zeker weet of er andere zaken afhankelijk hiervan zijn. Gebruik van virtualenv zou hierin natuurlijk uitkomst kunnen bieden!
3) Code-wise is de makkelijkste optie wat je zelf al aangaf: lazy loading. Ik zou dan gewoon de import statements verplaatsen bovenaan de functies waar je ze gebruikt. Python zorgt er zelf voor dat een tweede import call gecached is.
Dit heb ik onlangs nog getest en werkt inderdaad prima. Enig nadeel is dat ik vanuit de service (of script) ook de status van Domoticz nodig heb, welke weer via een request opgehaald moet worden en hierdoor dus niet onder de vertraging uit kom. De vertraging an sich is overigens niet eens zo bezwaarlijk, echter wil ik graag feedback geven op sommige events (bijvoorbeeld schakelen van het alarm d.m.v. een keyfob). Hierin wil ik de feedback graag asap weergeven (niets zo vervelend als op een knop drukken en pas reactie krijgen na 2 of 3 seconden). Deze feedback wordt nu gegeven door een buzzer via de Raspberry GPIO wat razendsnel werkt. Echter heb ik sirenes (zwave) op meerdere plekken geïnstalleerd welke ik hier liever voor zou gebruiken (betere spreiding / bereik).
4) Je kunt overwegen een virtualenv op een ramdisk te plaatsen. 't is een beetje van dik hout zaagt men planken, maar als 't werkt ;-)
Dit zou een optie zijn, maar zoals je zelf al laat doorschemeren wellicht één van de laatste. Mag ik hieruit concluderen dat je vermoed dat de vertraging (deels) te wijten is aan de snelheid van het filesystem? Alles draait nu op een sd-kaart. Ik heb begrepen dat mouten vanaf bijvoorbeeldeen USB-disk vele malen sneller zou zijn.
5) Met https://criu.org/Main_Page kan je een process dump maken en die vervolgens herladen. Dat is waarschijnlijk snel, maar levert wel een hoop complexiteit op.
Complexiteit probeer ik juist te mijden. Zolang je er actief mee bezig bent is alles goed te volgen, maar als je er na een tijdje weer in moet duiken is eenvoud vaak toch handig. Zoals ik eerder al zei ben ik nu eigenlijk al buiten de ideaalsituatie aan het werken.

Thanks voor je uitgebreide reactie :) , ik ga ermee aan de slag en zal mijn vorderingen posten!

  • farlane
  • Registratie: maart 2000
  • Laatst online: 16-06 23:09
My 2 cents:
- Als je latency wilt verkleinen heb je aan lazy loading geen hol. Laad liever alles wat je nodig hebt eenmalig vooraf (in een service?) en laat (niet laad) het in het geheugen. Dynamiek is funest voor latency.
- Pollen is niet slecht; it just works. Om je netjes te gedragen moet je wel tussendoor eventjes slapen.

Als de reactie van een systeem op een verandering van de status van een magneetschakelaar meer dan 100ms (pak 'em beet) op zich zou laten wachten, zou ik dat persoonlijk al langzaam vinden.

Je doelstelling om complexiteit te mijden kan ik alleen maar toejuichen.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • m.van.kan
  • Registratie: augustus 2015
  • Laatst online: 11-06 16:33
UPDATE;
Inmiddels dit project zo goed als afgerond. Ik heb ervoor gekozen om een worker-thread te maken die Domoticz events afvangt en de bijbehorende handelingen uitvoert (buzzer t.b.v. inloop- / uitloopvertraging, sirene activeren, notificatie verzenden, etc.). Deze service is zeer stabiel en de vertraging is (mede door gebruik te maken van async-calls) slechts 150 tot 200 ms, wat voor mij acceptabel is. En het grootste voordeel; alle code / logica is overzichtelijk verwerkt in één bestand (afgezien van een paar hulp-bibliotheken).

Dank voor jullie input


OnePlus 7 Pro (8GB intern) Microsoft Xbox One S All-Digital Edition LG OLED C9 Google Pixel 3a XL FIFA 19 Samsung Galaxy S10 Sony PlayStation 5 Microsoft

Tweakers vormt samen met Tweakers Elect, Hardware.Info, Autotrack, Nationale Vacaturebank, Intermediair en Independer de Persgroep Online Services B.V.
Alle rechten voorbehouden © 1998 - 2019 Hosting door True