Максимизация производительности Qt State Machines
Если вы разрабатываете приложения с Qt и используете конечные автоматы, вы, вероятно, пользуетесь фреймворком Qt state-machine. Вы можете определить машину на чистом C++ или SCXML, или генерировать C++ код из state-chart диаграмм. Эта статья оценивает эти опции в терминах функциональности, применимости и производительности.
Как разработчик ПО, вы, вероятно, написали бесчисленные switch-case блоки. Для многих это самый простой способ реализовать конечные автоматы, когда нет инструмента более высокого уровня. Однако по мере роста сложности поддержка становится вызовом, и рукотворная switch-case логика больше не устойчива.
Реализация конечных автоматов
Работая с объектно-ориентированным языком вроде C++, State pattern — проверенная стратегия. Она моделирует каждое состояние как класс, а переходы — как опциональные отдельные классы. Конечный автомат конструируется созданием экземпляров этих классов и их связыванием. Фреймворк может значительно снизить бойлерплейт.
Qt state-machine API позволяет конфигурировать машину с лаконичным кодом, оставляя семантику выполнения фреймворку. Хотя это упрощает реализацию, исходный код все равно может расти большим, когда машина содержит десятки или сотни состояний, делая общий дизайн трудным для понимания.
Qt также поддерживает State Chart XML (SCXML), стандарт W3C для описаний state-chart. Написание XML вручную утомительно, поэтому Qt Creator предлагает простой графический редактор.
Независимо от техники реализации, графическая модель — самый эффективный способ редактировать и понимать конечный автомат. Графические модели могут быть выражены в тексте через SCXML, или использованы для генерации исходного кода на любом языке, включая switch-case C++ или QStateMachine конструирование. Делегируя трансформацию инструменту, вы избегаете боли ручного кода и приводите все подходы к одному уровню удобства. Однако реализации остаются фундаментально отличными. Эта статья фокусируется на runtime поведении, особенно производительности.

Unsplash Фото Austris Augusts на Unsplash
Конкуренты
Как подходы отличаются в использовании CPU? Чтобы ответить, я создал набор бенчмарков. Первая часть сравнивает разные стратегии реализации. Это конкуренты:
- SCXML интерпретатор — тестовая машина определена в SCXML и исполняется классом Qt QSCXMLStateMachine.
- State pattern — тестовая машина реализована с использованием классов QStateMachine.
- Чистый C++ код — тестовая машина реализована C++ классом, использующим базовый switch-case подход.
Примечание: Код для этих примеров можно найти здесь.
Первые два варианта подразумевают использование Qt концепций вроде сигналов и слотов, а также очереди событий Qt, в то время как чистая C++ реализация не требует этой инфраструктуры. Чтобы сделать подходы более сравнимыми, тестовый набор включает два дополнительных сценария:
- Чистый C++ код с сигналами и слотами — та же реализация, но подключенная к системе сигнал/слот Qt.
- Чистый C++ код с QEvent — чистый C++ подход, маршрутизирующий события через очередь событий Qt.
Это позволяет изолировать влияние сигналов/слотов и очереди событий на время выполнения, сохраняя логику переходов состояний идентичной.
Тестовый конечный автомат
Для всех пяти вариантов я использовал конечный автомат, иллюстрированный на Рисунке 1 для базового тестового сценария.

Рисунок 1: Тестовый конечный автомат, созданный с YAKINDU Statechart Tools. (Источник: Автор)
Машина имеет шесть плоских состояний A–F, циклически проходит через них и реагирует на два входных события e1 и e2, чередующиеся триггерящие переходы. Действие каждого перехода добавляет 10 к переменной x; переход из F в A также эмитит выходное событие o.

Рисунок 2: Тестовый конечный автомат как SCXML модель в Qt Creator. (Источник: Автор)
Я определил машину с YAKINDU Statechart Tools, которые могут генерировать как SCXML, так и чистый switch-case C++ код. Сгенерированный код также поддерживает Qt сигналы и слоты. Я реализовал вариант QStateMachine вручную, но все остальные реализации происходят из того же state-chart определения.
Каждый тест запускал один миллион итераций одного цикла состояния. Цикл начинается и заканчивается с активным состоянием A, и обрабатывает 6 миллионов входных событий, 1 миллион выходных событий и их ассоциированные действия перехода. Тесты выполнялись как командная-строчка приложение, и время для всего батча записывалось. Время на событие выводилось делением общего времени на количество обработанных событий. Использовались наименьшие значения из повторных запусков.
Бенчмарки выполнялись на MacBook Pro 2014 года с 2.4 GHz quad-core i7 в оптимизированном release билде. Точные числа будут варьироваться на другом железе, но относительные различия остаются последовательными.
Фигуры производительности
Обработка одного события на чистом C++ заняла в среднем 7 нс, в то время как SCXML потребовал 33 850 нс — фактор примерно 4 800. Использование сигналов и слотов добавило ~72 нс на событие (~90% накладные расходы). Маршрутизация через очередь событий Qt добавила ~731 нс на событие (~10x сигналов, ~100x чистый C++). После вычитания накладных расходов очереди, фреймворк QStateMachine стоит ~4 500 нс на событие — примерно в 635 раз медленнее чистого C++. SCXML интерпретатор, интерпретирующий JavaScript, добавляет еще ~7x фактор, делая его самым медленным вариантом.

Рисунок 3: Время обработки одного события сравнено. (Источник: Автор)
Влияние иерархии и ортогональности
Я также профилировал иерархические и ортогональные варианты той же машины. Иерархическая машина (Рисунок 4) и ортогональная машина (Рисунок 5) имеют идентичное поведение, но разную структуру.

Рисунок 4: Иерархический тестовый statechart. (Источник: Автор)

Рисунок 5: Ортогональный тестовый statechart. (Источник: Автор)
Результаты, показанные на Рисунке 6, указывают, что добавление иерархии не вводит измеримых накладных расходов в любой реализации. Ортогональность также не удваивает время выполнения, как можно ожидать; увеличение составляет около 2.4x для чистого C++ и 3.1x для SCXML, далеко ниже теоретической четырехкратной стоимости.

Рисунок 6: Влияние иерархий и ортогональности на производительность. (Источник: Автор)
Заключение
Чистый C++ доставляет лучшую производительность. Сигналы, слоты и очередь событий Qt обменивают скорость на удобство и поддерживаемость, в то время как фреймворк QStateMachine — самый медленный из трех основных вариантов. SCXML часто достаточно быстр для интерактивных приложений и предлагает преимущество runtime реконфигурации. Когда максимальная скорость критична, рукотворный switch-case код — рекомендуемый подход.
Встроенный
- Выбор правильного CAD-ПО для дизайна ювелирных изделий: полное руководство
- Топ брендов станков с ЧПУ для точности и надежности
- Выбор идеального станка с ЧПУ: практическое руководство
- Освоение экстренной подготовленности склада: исчерпывающее руководство
- Эффективные методы мониторинга производительности технического персонала
- Как выбрать идеальный тормоз ветряка для надежной производительности
- Экспертное руководство: выбор идеальной картонирующей машины для вашей линии
- Как выбрать идеальный водорез для вашего цеха
- Выбор станка для гибки листового металла: полное руководство
- Выбор правильного погружного насоса: экспертные советы и ключевые характеристики