Qtステートマシンのパフォーマンスを最大化:実装手法別ベンチマーク
Qtでアプリケーションを開発しステートマシンを使用しているなら、Qtのステートマシンフレームワークを使っているはずです。プレーンC++、SCXML、またはステートチャート図から生成されたC++コードでマシンを定義できます。この記事では、これらの選択肢を機能性、適用性、パフォーマンスの観点で評価します。
ソフトウェア開発者として、無数のswitch-caseブロックを書いてきたでしょう。高レベルツールがない場合、これはステートマシン実装の最も簡単な方法です。しかし複雑性が増すと保守が課題となり、手作りのswitch-caseロジックはもはや持続可能ではありません。
ステートマシンの実装
C++のようなオブジェクト指向言語では、ステートパターンが実証済みの戦略です。各状態をクラスとしてモデル化し、遷移をオプションで別クラスとします。これらのクラスのインスタンスを作成し結線することでステートマシンを構築します。フレームワークはボイラープレートを大幅に削減できます。
QtのステートマシンAPIを使えば、簡潔なコードでマシンを設定し、実行セマンティクスをフレームワークに任せられます。実装は簡素化されますが、数十〜数百の状態を含むマシンではソースコードが肥大化し、全体設計の把握が困難になります。
QtはState Chart XML(SCXML)、W3Cのステートチャート記述標準もサポートします。XMLを手書きするのは面倒なので、Qt Creatorは簡易グラフィカルエディターを提供します。
実装技法に関わらず、グラフィカルモデルがステートマシンの編集と理解に最も効果的です。グラフィカルモデルはSCXMLでテキスト表現可能であり、任意の言語(switch-case C++やQStateMachine構築を含む)へのソースコード生成にも利用できます。変換をツールに委任することで手作業コードの苦痛を回避し、すべてのアプローチを同一のユーザビリティレベルに引き上げられます。しかし実装は根本的に異なります。この記事はランタイム動作、特にパフォーマンスに焦点を当てます。

Unsplash Photo by Austris Augusts on Unsplash
競合手法
CPU使用量の面から、各アプローチはどう異なるか?これを答えるためベンチマークスイートを作成しました。第一部では異なる実装戦略を比較。競合は以下の通り:
- SCXMLインタープリター — テストマシンをSCXMLで定義し、QtのQSCXMLStateMachineクラスで実行
- ステートパターン — テストマシンをQStateMachineクラスで実装
- プレーンC++コード — 基本的なswitch-caseアプローチを用いるC++クラスで実装
注:これらの例のコードはこちらで入手可能です。
最初の2変種は、シグナル/スロットやQtイベントキューなどのQt概念の使用を伴いますが、プレーンC++実装はこのインフラを必要としません。アプローチをより比較可能にするため、テストスイートには2つの追加シナリオを含めました:
- シグナル/スロット付きプレーンC++コード — 上記と同一実装だがQtのシグナル/スロットシステムに接続
- QEvent付きプレーンC++コード — プレーンC++アプローチでイベントをQtイベントキュー経由でルーティング
これにより、コアステート遷移ロジックを同一に保ちつつ、シグナル/スロットとイベントキューの実行時間への影響を分離できます。
テスト用ステートマシン
すべての5変種で、基本テストシナリオとして図1のステートマシンを使用しました。

図1: YAKINDU Statechart Toolsで作成したテストステートマシン(出典:著者)
このマシンはA〜Fの6つのフラット状態を持ち、それらを巡回し、交互に遷移をトリガーする2つの入力イベントe1とe2に反応します。各遷移アクションは変数xに10を加算し、FからAへの遷移では出力イベントoも発行します。

図2: Qt CreatorでのSCXMLモデルとしてのテストステートマシン(出典:著者)
マシンはYAKINDU Statechart Toolsで定義し、SCXMLとプレーンswitch-case C++コードの両方を生成可能です。生成コードはQtシグナル/スロットもサポートします。QStateMachine変種は手動実装しましたが、他のすべての実装は同じステートチャート定義から派生しています。
各テストは、単一ステートループの100万回反復を実行。ループは状態Aアクティブで開始・終了し、600万入力イベント、100万出力イベント、および関連遷移アクションを処理。テストはコマンドラインアプリケーションとして実行し、バッチ全体の時間を記録。イベントあたりの時間は総時間を処理イベント数で割って算出。繰り返し実行の最小値を採用。
ベンチマークは、2.4GHzクアッドコアi7搭載の2014年MacBook Proで、最適化済みリリースビルドで実行。正確な数値はハードウェアにより変動しますが、相対差は一貫しています。
パフォーマンス数値
プレーンC++で単一イベント処理に平均7ns、SCXMLでは33,850ns — 約4,800倍の差。シグナル/スロット使用でイベントあたり約72ns追加(約90%オーバーヘッド)。Qtイベントキュー経由のルーティングで約731ns追加(シグナル/スロットの約10倍、プレーンC++の約100倍)。キューのオーバーヘッドを除くと、QStateMachineフレームワークのコストはイベントあたり約4,500ns — プレーンC++の約635倍遅い。SCXMLインタープリターはJavaScriptを解釈するためさらに約7倍の係数が加わり、最も遅い変種となります。

図3: 単一イベント処理時間の比較(出典:著者)
階層性と直交性の影響
同一振る舞いだが構造が異なる階層型と直交型のマシンもプロファイリングしました。階層型マシン(図4)と直交型マシン(図5)は同一振る舞いですが構造が異なります。

図4: 階層型テストステートチャート(出典:著者)

図5: 直交型テストステートチャート(出典:著者)
結果(図6)は、階層追加がどちらの実装でも測定可能なオーバーヘッドを導入しないことを示します。直交性も理論上の4倍コストには遠く及ばず、プレーンC++で約2.4倍、SCXMLで約3.1倍の増加に留まります。

図6: 階層と直交性のパフォーマンス影響(出典:著者)
結論
プレーンC++が最高のパフォーマンスを発揮します。シグナル/スロットとQtイベントキューは速度を犠牲に利便性と保守性を得ます。QStateMachineフレームワークは3つのコア変種中最遅。SCXMLはインタラクティブアプリケーションには十分高速なことが多く、ランタイム再構成の利点があります。最大速度がクリティカルな場合、手作りswitch-caseコードが推奨されます。
埋め込み
- 適切な潜水ポンプの選び方:エキスパートのヒントと主要機能
- 高性能電動チューブベンダー:精度、信頼性、コスト効率
- ジュエリーデザインに最適なCADソフトウェア選び: 総合ガイド
- 倉庫の緊急事態対応をマスターする:包括的ガイド
- 技術スタッフのパフォーマンス監視:効果的な手法
- 世界トップクラスのCNC工作機械ブランド7選|精度と信頼性で選ぶ
- 最適なCNC工作機械選びの実践ガイド|見逃せない4つの重要特性
- 信頼性の高い風力タービンブレーキ選定ガイド
- 専門家ガイド:生産ラインに最適なカートニングマシンの選び方
- ウォータージェット切断機の選び方:あなたの機械加工ショップに最適な1台を見つける
- 適切な板金折り曲げ機の選び方:包括的ガイド