Prestaties van Qt State Machines maximaliseren
Als u applicaties ontwikkelt met Qt en state machines gebruikt, gebruikt u waarschijnlijk Qt's state-machine-framework. U kunt de machine definiëren in plain C++ of SCXML, of C++-code genereren vanuit state-chart-diagrammen. Dit artikel evalueert deze opties qua functionaliteit, toepasbaarheid en prestaties.
Als software-ontwikkelaar heeft u waarschijnlijk oneindige switch-case-blokken geschreven. Voor velen is dit de eenvoudigste manier om state machines te implementeren wanneer geen higher-level-tool beschikbaar is. Echter, naarmate complexiteit groeit, wordt onderhoud een uitdaging, en handgemaakte switch-case-logica is niet langer houdbaar.
State machines implementeren
Wanneer u werkt met een object-georiënteerde taal zoals C++, is het state-pattern een bewezen strategie. Het modeleert elke state als een klasse, en transities als optionele afzonderlijke klassen. Een state-machine wordt geconstrueerd door instanties van deze klassen te creëren en ze samen te draaien. Een framework kan boilerplate aanzienlijk verminderen.
Qt's state-machine-API laat u een machine configureren met beknopte code, wat de uitvoerings-semantiek aan het framework overlaat. Hoewel dit implementatie vereenvoudigt, kan de broncode nog steeds groot worden wanneer een machine tientallen of honderden states bevat, wat het algehele ontwerp moeilijk te begrijpen maakt.
Qt ondersteunt ook State Chart XML (SCXML), de W3C-standaard voor state-chart-beschrijvingen. XML handmatig schrijven is tedieus, dus Qt Creator biedt een eenvoudige grafische editor.
Ongeacht de implementatietechniek, is een grafisch model de meest effectieve manier om een state-machine te bewerken en begrijpen. Grafische modellen kunnen in tekst uitgedrukt worden via SCXML, of gebruikt worden om broncode te genereren in elke taal, inclusief switch-case C++ of QStateMachine-constructie. Door de transformatie aan een tool te delegeren, vermijdt u de pijn van handmatige code en brengt u alle benaderingen op hetzelfde bruikbaarheidsniveau. De implementaties blijven echter fundamenteel verschillend. Dit artikel focust op runtime-gedrag, met name prestaties.
Unsplash Foto door Austris Augusts op Unsplash
De concurrenten
Hoe verschillen de benaderingen in CPU-gebruik? Om dit te beantwoorden, creëerde ik een benchmark-suite. Het eerste deel vergelijkt de verschillende implementatiestrategieën. Dit zijn de concurrenten:
- SCXML-interpreter - de test-machine is gedefinieerd in SCXML en uitgevoerd door Qt's QSCXMLStateMachine-klasse.
- State-pattern - de test-machine is geïmplementeerd met QStateMachine-klassen.
- Plain C++-code - de test-machine is geïmplementeerd door een C++-klasse die een basale switch-case-benadering gebruikt.
Let op: Code voor deze voorbeelden is hier te vinden.
De eerste twee varianten impliceren het gebruik van Qt-concepten zoals signals en slots alsook het gebruik van een Qt-event-queue, terwijl een plain C++-implementatie deze infrastructuur niet vereist. Om de benaderingen vergelijkbaarder te maken, bevat de test-suite twee additionele scenario's:
- Plain C++-code met signals en slots - dezelfde implementatie als boven maar aangesloten op Qt's signal/slot-systeem.
- Plain C++-code met QEvent - de plain C++-benadering die events door de Qt-event-queue routeert.
Dit stelt ons in staat om de impact van signals/slots en de event-queue op uitvoeringstijd te isoleren terwijl de kern state-transitie-logica identiek blijft.
De test-state-machine
Voor alle vijf varianten gebruikte ik de state-machine geïllustreerd in Figuur 1 voor het basis-testscenario.
Figuur 1: De test-state-machine, zoals gecreëerd met YAKINDU Statechart Tools. (Bron: Auteur)
De machine heeft zes platte states A-F, cycelt door ze heen, en reageert op twee input-events e1 en e2 die afwisselend transities triggeren. Elke transitie-actie voegt 10 toe aan een variabele x; de transitie van F naar A emiteert ook het output-event o.
Figuur 2: De test-state-machine als SCXML-model in Qt Creator. (Bron: Auteur)
Ik definieerde de machine met YAKINDU Statechart Tools, die zowel SCXML als plain switch-case C++-code kan genereren. De gegenereerde code ondersteunt ook Qt signals en slots. Ik implementeerde de QStateMachine-variant handmatig, maar alle andere implementaties stammen van dezelfde state-chart-definitie.
Elke test liep één miljoen iteraties van een enkele state-lus. Een lus start en eindigt met state A actief, en verwerkt de 6 miljoen input-events, 1 miljoen output-events en hun geassocieerde transitie-acties. De tests werden uitgevoerd als een command-line-applicatie, en de tijd voor de hele batch werd recorded. De per-event-tijd werd afgeleid door de totale tijd te delen door het aantal verwerkte events. De laagste waarden uit herhaalde runs werden gebruikt.
Benchmarks werden uitgevoerd op een 2014 MacBook Pro met een 2,4 GHz quad-core i7 in een geoptimaliseerde release-build. Exacte getallen zullen op andere hardware variëren, maar relatieve verschillen blijven consistent.
Prestatiecijfers
Het verwerken van een enkel event met plain C++ nam gemiddeld 7 ns, terwijl SCXML 33.850 ns vereiste - een factor van ongeveer 4.800. Het gebruik van signals en slots voegde ~72 ns per event toe (~90% overhead). Routing via de Qt-event-queue voegde ~731 ns per event toe (~10x signals, ~100x plain C++). Na aftrek van queue-overhead, kost het QStateMachine-framework ~4.500 ns per event - ongeveer 635x langzamer dan plain C++. De SCXML-interpreter, die JavaScript interpreteert, voegt nog een ~7x factor toe, wat het de langzaamste variant maakt.
Figuur 3: Enkel event-verwerkingstijd vergeleken. (Bron: Auteur)
Impact van hiërarchie en orthogonaliteit
Ik profileerde ook hiërarchische en orthogonale varianten van dezelfde machine. De hiërarchische machine (Figuur 4) en de orthogonale machine (Figuur 5) hebben identiek gedrag maar verschillende structuur.
Figuur 4: Hiërarchische test-statechart. (Bron: Auteur)
Figuur 5: Orthogonale test-statechart. (Bron: Auteur)
Resultaten, getoond in Figuur 6, geven aan dat het toevoegen van hiërarchie geen meetbare overhead introduceert in beide implementaties. Orthogonaliteit verdubbelt de runtime ook niet zoals men zou verwachten; de toename is ongeveer 2,4x voor plain C++ en 3,1x voor SCXML, ver onder de theoretische viervoudige kost.
Figuur 6: Prestatie-impact van hiërarchieën en orthogonaliteit. (Bron: Auteur)
Conclusie
Plain C++ levert de beste prestaties. Signals, slots en de Qt-event-queue ruilen snelheid voor gemak en maintainability, terwijl het QStateMachine-framework de langzaamste van de drie kernvarianten is. SCXML is vaak snel genoeg voor interactieve toepassingen en biedt het voordeel van runtime-herconfiguratie. Wanneer maximale snelheid kritiek is, is handgemaakte switch-case-code de aanbevolen benadering.
Ingebed
- De juiste potgietpomp kiezen: expert tips & sleutelkenmerken
- Premium Elektrische Buigmachine: Precisie, Betrouwbaarheid en Kostenefficiëntie
- De Juiste CAD-software Kiezen Voor Juwelierontwerp: Uitgebreide Gids
- Magazijn-noodvoorziening meesteren: een uitgebreide gids
- Effectieve technieken voor het monitoren van technische personeelsprestaties
- Top CNC-Machine Merken voor Precisie en Betrouwbaarheid
- De Ideale CNC-Machine Kiezen: Een Praktische Gids
- Hoe De Ideale Windturbine Rem Te Kiezen Voor Betrouwbare Prestaties
- Expertgids: De Ideale Inlegmachine Selecteren voor Uw Productielijn
- De ideale waterjetsnijmachine kiezen voor uw machinewerkplaats
- De juiste platenvouwmachine kiezen: een uitgebreide gids