Hoe de beste Qt-statusmachineprestaties te garanderen
Als je Qt gebruikt voor applicatie-ontwikkeling en als je state machines gebruikt, dan is het waarschijnlijk dat je gebruik maakt van het Qt state machine framework. U definieert dus de statusmachine met behulp van gewone C++ of SCXML. Een alternatieve benadering is het genereren van C++-code uit toestandsmachinediagrammen. In dit artikel worden deze benaderingen vergeleken, waarbij rekening wordt gehouden met functionaliteit, toepasbaarheid en prestaties.
Ik wed dat je als softwareontwikkelaar al tonnen min of meer gecompliceerde switch-case-statements hebt geïmplementeerd. Dit geldt in ieder geval voor mij, en veel van deze switch-case-codering was in wezen niets anders dan het implementeren van verschillende statusmachines. Dit is de gemakkelijkste manier om state-machines te programmeren als je niets anders bij de hand hebt dan de programmeertaal van je keuze. Hoewel het begin eenvoudig is, wordt dergelijke code steeds minder onderhoudbaar naarmate de complexiteit van de toestandsmachine toeneemt. Aan het einde zult u ervan overtuigd zijn dat u op deze manier niet verder wilt gaan met het handmatig implementeren van statusmachines. (Trouwens – ik neem aan dat je weet wat staatsmachines zijn.)
Statusmachines implementeren
Er zijn verschillende alternatieven om staatsmachines te implementeren. Een van de betere manieren - vooral wanneer u een objectgeoriënteerde programmeertaal zoals C++ gebruikt - is het toepassen van het statuspatroon. Deze benadering maakt gebruik van toestandsklassen en vaak ook overgangsklassen. Een toestandsmachine wordt vervolgens gedefinieerd door instanties van toestandsklassen te creëren en deze te bedraden met behulp van instanties van overgangsklassen. In dit geval helpt een framework enorm om de codegrootte en implementatie-inspanningen te verminderen.
Het Qt state machine framework is een goed voorbeeld. Met deze API kunt u een statusmachine "configureren" met behulp van compacte code. U hoeft zich geen zorgen te maken over details van de uitvoeringssemantiek van de staatsmachine, omdat deze al door het framework zijn geïmplementeerd. Je moet nog steeds code schrijven, en naarmate je toestandsmachine complexer wordt en enkele tientallen of zelfs honderden toestanden bevat, wordt het heel moeilijk om een overzicht te krijgen. Een foto zegt meer dan duizend woorden, en het bekende concept van toestandsdiagrammen helpt deze beperking te overwinnen. Qt zelf biedt ondersteuning voor State Chart XML (SCXML), een W3C-standaard. Omdat het handmatig schrijven van XML niet leuk is,Qt Creator bevat ook een eenvoudige grafische editor voor toestandsdiagrammen.
Ongeacht de concrete implementatiebenadering, is het gebruik van een grafische syntaxis de beste keuze voor het bewerken en begrijpen van statusmachines. Dergelijke grafische modellen kunnen niet alleen tekstueel worden weergegeven door talen zoals SCXML, maar kunnen ook worden gebruikt om elke broncode van programmeertalen te genereren, zoals een switch-case-gebaseerde toestandmachine in gewone C++ - of C++-code die instanties van QStateMachine. Door een tool te gebruiken die zo'n transformatie voor je doet, kun je de pijn van het met de hand schrijven van machinecode vermijden. Het tilt alle drie de implementatiebenaderingen naar hetzelfde bruikbaarheidsniveau. Toch zijn de implementaties nog steeds fundamenteel anders. Dit artikel gaat over het vergelijken van hun runtime-gedrag en vooral hun prestaties.
UnsplashPhoto door Austris Augusts op Unsplash
De concurrenten
Dus hoe zit het met de prestaties? Hoe verschillen de beschikbare benaderingen met betrekking tot de vereiste CPU-cycli? Om wat concrete cijfers te krijgen, heb ik een prestatietestsuite opgezet. Het eerste deel vergelijkt de verschillende implementatiestrategieën. Dit zijn de concurrenten:
- SCXML-interpreter – de teststatusmachine wordt gedefinieerd met SCXML en uitgevoerd door Qt's QSCXMLStateMachine klas.
- Statuspatroon – de teststatusmachine is geïmplementeerd met behulp van QStateMachine lessen.
- Gewone C++-code - de teststatusmachine wordt geïmplementeerd door een C++-klasse die een basisbenadering op basis van switchcases toepast.
Opmerking:de code voor deze voorbeelden vindt u hier.
De eerste twee varianten impliceren het gebruik van Qt-concepten zoals signalen en slots, evenals het gebruik van een Qt-gebeurteniswachtrij, terwijl een eenvoudige C++-implementatie deze infrastructuur niet vereist. Om de benaderingen beter vergelijkbaar te maken, bevat de testsuite nog twee testscenario's:
- Gewone C++-code met signalen en slots - de teststatusmachine heeft dezelfde implementatie als hierboven beschreven, maar gebruikt signalen en slots om het in de applicatie te integreren.
- Gewone C++-code met QEvent – gebruikt de gewone C++-codebenadering maar maakt gebruik van de Qt-gebeurteniswachtrij voor verwerking in en uit evenementen.
Dit maakt het mogelijk om de impact van het gebruik van signalen en slots enerzijds en het gebruik van QEvents te vergelijken aan de andere kant in vergelijking met de gewone C++-implementatie, omdat de uitvoeringscode van de staatsmachine in alle gevallen identiek is, maar alleen anders is verpakt.
De teststatusmachine
Voor het testen van alle vijf concurrenten heb ik de toestandsmachine gedefinieerd die wordt getoond in Fig. 1 voor het basistestscenario.
Figuur 1:De teststatusmachine, zoals gemaakt met YAKINDU Statechart Tools. (Bron:Auteur)
De testtoestandsmachine is een eenvoudige vlakke toestandsmachine. Het definieert zes toestanden A tot F en fietst door de staten. Twee invoergebeurtenissen e1 en e2 zijn gedefinieerd, die afwisselend toestandsovergangen activeren. Wanneer een toestandsovergang plaatsvindt, wordt ook een eenvoudige actie uitgevoerd. Elke overgangsactie voegt gewoon 10 toe aan een statechart-variabele met de naam x . De overgang van toestand F naar A verhoogt (of zendt) bovendien de uit evenement o .
Figuur 2:De teststatusmachine als SCXML-model in Qt Creator. (Bron:Auteur)
Deze statusmachine is gedefinieerd met behulp van YAKINDU Statechart Tools, die het genereren van SCXML ondersteunt. Deze SCXML kan worden toegevoegd aan het Qt-project en kan worden bewerkt in Qt Creator. Zoals je kunt zien in afb. 2, heeft de toestandsmachine dezelfde structuur als die in Fig. 1, maar sommige details, zoals de overgangsacties, zijn niet zichtbaar in Qt Creator. YAKINDU Statechart Tools bieden nog enkele voordelen, maar die zal ik hier niet bespreken.
Belangrijker hier is het feit dat YAKINDU Statechart Tools ook gewone, switch-case-gebaseerde C++ state-machineklassen kan genereren. Het biedt ook een optie om Qt-enabled klassen te genereren met signalen en slots, dus dit is handig. Met deze tool hoefde ik alleen de state-patroongebaseerde state-machine te implementeren met behulp van QStateMachine met de hand. Voor die variant was geen codegenerator beschikbaar. Desalniettemin was ik in staat om veel implementatie-inspanningen te besparen, terwijl ik semantisch equivalente toestandsmachines kreeg voor de prestatietests door slechts één enkele statechart-definitie te gebruiken.
Alle testgevallen volgen hetzelfde schema. Omdat ik de gemiddelde tijd wilde meten die nodig was om individuele gebeurtenissen te verwerken, legde elke test een miljoen herhalingen van een enkele toestandslus vast. Elke toestandslus voert alle gebeurtenissen uit die nodig zijn om alle toestanden te bezoeken en alle overgangen en overgangsacties te verwerken. Een toestandslus begint en eindigt dus met toestand A actief zijn. Dit betekent dat voor elke testcase 6 miljoen in evenementen en overgangsacties en 1 miljoen uit gebeurtenissen met de bijbehorende overgangsacties worden verwerkt. De tests werden uitgevoerd als een opdrachtregeltoepassing en registreerden de tijd voor de iteraties als een enkele batch. Het tijdverbruik per gebeurtenis kan dan eenvoudig worden bepaald door de gemeten tijd te delen door de som van het aantal in evenementen en uit evenementen. De tests zijn meerdere keren uitgevoerd en de meetresultaten met de laagste waarden zijn gekozen.
De tests zijn uitgevoerd met geoptimaliseerde code zonder foutopsporingsinformatie op mijn oude (medio 2014) MacBook Pro, met Core i7 Quad Core CPU 2,4 GHz. Natuurlijk zullen de concrete aantallen verschillen op verschillende machines en besturingssystemen. Dit is echter niet relevant, omdat ik de verschillende implementatiebenaderingen ten opzichte van elkaar wilde vergelijken. Deze relatieve verschillen zullen vergelijkbaar zijn op verschillende hardware- en OS-platforms.
Laten we eens kijken naar de prestatiecijfers
Ja, ik denk dat bijna iedereen had verwacht dat een eenvoudige C++-implementatie sneller zou zijn dan de andere alternatieven, maar de omvang van de verschillen is echt verbazingwekkend.
Figuur 3:Verwerkingstijd voor één gebeurtenis vergeleken. (Bron:Auteur)
Het verwerken van afzonderlijke gebeurtenissen met gewoon C++ duurde gemiddeld 7 nanoseconden. Het gebruik van SCXML vereiste 33.850 nanoseconden - dat is een factor van ongeveer 4800 en een enorm verschil! Ter vergelijking:licht legt meer dan 10 kilometer af, terwijl de SCXML-statusmachine slechts een enkele overgang verwerkt, terwijl dezelfde overgang in de gewone C++-statusmachine slechts zo veel tijd overlaat voor licht om iets meer dan 2 meter te reizen. Dit impliceert zeer verschillende ordes van grootte voor CPU-cycli en energieverbruik.
Uiteraard zijn de concrete aantallen afhankelijk van de machine en van de gebruikte testprocedure. Ik zal dit onderwerp later bespreken. Maar laten we eerst de andere nummers bespreken. De eerste drie testscenario's bevatten allemaal een identieke statusovergangslogica, die is gegenereerd door YAKINDU Statechart Tools, maar elk op verschillende manieren verpakt.
Het gebruik van signalen en slots voor het afhandelen van gebeurtenissen kostte gemiddeld 72ns bij gebruik van directe verbindingen. Dit mechanisme legt dus een minimale overhead van ~90% op, vergeleken met de werkelijke machinelogica. Op dit punt wil ik niet beweren dat het gebruik van signalen en slots applicaties traag maakt. In plaats daarvan zou ik liever beweren dat implementaties van gewone code van state-machines extreem snel zijn .
Een vergelijking met het derde scenario geeft een goed beeld van de prestatieoverhead die wordt veroorzaakt door het gebruik van de eventwachtrij. In dit scenario worden alle statechart-gebeurtenissen door de gebeurteniswachtrij geleid. Met 731ns per gebeurtenis kost het een factor ~10 vergeleken met signalen en slots en ~100 vergeleken met gewoon C++.
We kunnen aannemen dat een vergelijkbare overhead ook geldt voor de andere twee scenario's “plain QStateMachine " en "SCXML state machine" - ze hebben allebei een actieve gebeurteniswachtrij nodig. Dus als de veronderstelde overhead van de gebeurteniswachtrij wordt afgetrokken van de 5200ns per gebeurtenis, krijgen we een ruw tijdverbruik van de QStateMachine kader van 4500ns per gebeurtenis. Vergeleken met de gewone code-aanpak, zijn QStateMachine-gebaseerde implementaties van state-machines traag. Dit is een factor van ongeveer 635, vergeleken met de gewone C++ code-implementatie.
Laten we tot slot eens kijken naar de SCXML-interpreter. Het omvat het interpreteren van JavaScript-code en voegt nog een factor van ~7 toe. Vergeleken met de gewone code-aanpak, zijn SCXML-gebaseerde implementaties van state-machines erg traag.
Hoe zit het met hiërarchische en orthogonale toestandsmachines?
Tot nu toe heb ik een eenvoudige flat state machine geprofileerd. Maar statecharts bieden veel meer functies, en de twee belangrijkste structurele kenmerken zijn hiërarchie en orthogonaliteit. Dus, welke impact heeft het gebruik van deze functies met betrekking tot de runtime van de machine?
Ten eerste heb ik, om de impact van hiërarchieën te meten, een hiërarchische variant van de te profileren staatsmachine gedefinieerd, getoond in Fig. 4.
Figuur 4:Hiërarchische test statechart. (Bron:Auteur)
Het biedt precies hetzelfde gedrag als de machine met platte toestanden, maar voegt enkele samengestelde toestanden toe. Door de functionaliteit identiek te houden, maar alleen de structuur te veranderen, kan worden nagegaan hoeveel overhead de structurele variant met zich meebrengt, indien aanwezig.
Ten tweede, om de impact van orthogonaliteit te meten, heb ik de flat state machine gerepliceerd in de vorm van vier orthogonale regio's. Ze hebben allemaal precies dezelfde functionaliteit. De resulterende toestandsmachine (zie fig. 5) zal dus vier keer zoveel werk doen als de eenvoudige toestandsmachine.
Figuur 5:Orthogonale test statechart. (Bron:Auteur)
Voor profilering koos ik de gewone C++- en de SCXML-implementaties, omdat dit de snelste en langzaamste varianten waren. Het schema in afb. 6 toont de resultaten. Het is zeer bemoedigend dat het gebruik van hiërarchieën in statecharts geen meetbare prestatie-impact heeft in beide implementatievarianten.
Figuur 6:Prestatie-impact van hiërarchieën en orthogonaliteit. (Bron:Auteur)
Een ander positief resultaat is dat het gebruik van orthogonaliteit ook geen negatieve impact heeft. Integendeel, terwijl je zou verwachten dat de verwerkingstijd vier keer zo groot was om vier keer zoveel werk te doen, is de effectieve toename van de runtime met de factoren ~2.4 en ~3.1 aanzienlijk kleiner dan 4.
Waarom is dit het geval? De reden hiervoor is dat er een algemeen deel van de verwerking van toestandsmachines is dat onafhankelijk is van de verwerking van individuele toestanden en gebeurtenissen. Dit deel kost 52% (of 3,5ns per gebeurtenis) voor de gewone C++-statusmachine, vergeleken met 28% (of 9300ns per gebeurtenis) voor SCXML. Ten slotte hebben orthogonale toestanden minder impact bij het gebruik van gegenereerde C++-code in vergelijking met SCXML.
Conclusie
Gewoon C++ is veel efficiënter dan alle alternatieven. Het gebruik van signalen en slots of de Qt-gebeurteniswachtrij zijn raamwerkmechanismen die het implementeren en onderhouden van complexe toestandsmachine-applicaties vergemakkelijken. Het Qt state machine framework vereist beide mechanismen. Met behulp van gegenereerde C++-code heb je de keuze.
In veel scenario's, vooral interactieve, zijn zelfs SCXML-statusmachines snel genoeg en kunnen ze meer flexibiliteit bieden door het gedrag configureerbaar te maken door tijdens runtime van statusdiagramdefinities te wisselen.
Ingebed
- Hoe u de beste CAD-software voor sieradenontwerp kiest
- De beste CNC-merken
- De juiste CNC-machine kiezen
- Hoe zorg je voor paraatheid bij noodsituaties in het magazijn
- Hoe de prestaties van technisch personeel monitoren?
- Hoe u de beste windturbinerem kiest?
- Hoe u de juiste kartonneermachine kiest?
- Hoe u de juiste waterstraalsnijmachine kiest
- Hoe de beste plaatwerkvouwmachine te kiezen?
- Hoe de beste dompelpomp kiezen?
- De beste buigmachine:de elektrische buizenbuiger