如何保证Qt状态机的最佳性能
如何保證Qt狀態機的最佳性能
How to ensure the best Qt state machine performance
如果您使用Qt進行應用程序開發,并且使用狀態機,那么很可能您正在使用Qt狀態機框架。因此,您將使用普通C++或SCXML定義狀態機。另一種方法是從狀態機圖生成C++代碼。本文比較了這些方法,并將功能性、適用性和性能考慮在內。
我敢打賭,作為一個軟件開發人員,您已經實現了大量或多或少復雜的switch case語句。這至少對我來說是正確的,而且這種交換情況編碼基本上只是實現不同的狀態機。如果手頭除了您選擇的編程語言之外沒有其他任何東西,那么這是啟動狀態機編程的最簡單方法。雖然開始很容易,但是隨著狀態機復雜性的增加,這樣的代碼變得越來越不可維護。最后,您將確信您不希望繼續以這種方式手動實現狀態機。(順便說一句,我假設您知道什么是狀態機。)
實現狀態機
有不同的方法來實現狀態機。其中一個更好的方法——尤其是當你使用像C++這樣的面向對象編程語言——正在應用狀態模式。這種方法使用狀態類,通常也使用轉換類。然后,通過創建狀態類的實例并使用轉換類的實例連接它們來定義狀態機。在這種情況下,框架有助于減少代碼大小和實現工作量。
Qt狀態機框架就是一個很好的例子。這個API允許您使用緊湊代碼“配置”狀態機。您不必關心狀態機執行語義的細節,因為框架已經實現了這些細節。您仍然需要編寫代碼,并且隨著狀態機變得越來越復雜,并且包含幾十個甚至數百個狀態,因此很難獲得概述。一幅圖勝過千言萬語,眾所周知的狀態圖概念有助于克服這一限制。Qt本身提供了對狀態圖XML(SCXML)的支持,SCXML是W3C標準。由于手工編寫XML并不有趣,Qt-Creator還包含一個簡單的圖形狀態圖編輯器。
不管具體的實現方法如何,使用圖形語法是編輯和理解狀態機的最佳選擇。這樣的圖形模型不僅可以用像SCXML這樣的語言來表示,還可以用來生成任何類型的編程語言源代碼,比如基于C++的基于實例的狀態機,或者C++代碼,它建立QSTATEMACHEN的實例。使用一個可以為您進行這種轉換的工具,您可以避免手工編寫狀態機代碼的痛苦。它將所有三種實現方法提升到相同的可用性級別。盡管如此,實現仍然是根本不同的。本文將比較它們的運行時行為,特別是它們的性能。
競爭對手
那么性能呢?關于所需的CPU周期,可用的方法有什么不同?為了得到一些具體的數字,我建立了一個性能測試套件。第一部分比較了不同的實施策略。以下是競爭對手:
SCXML解釋器–測試狀態機是使用SCXML定義的,并由Qt的QSCXMLStateMachine類執行。
狀態模式–測試狀態機是使用QStateMachine類實現的。
普通C++代碼——測試狀態機是由C++類實現的,該類應用了一個基本的基于交換機實例的方法。
注意:這些例子的代碼可以在這里找到。
前兩個變體意味著使用QT概念,如信號和時隙以及QT事件隊列的使用,而普通C++實現不需要這種基礎結構。為了使這些方法更具可比性,測試套件還包括兩個測試場景:
帶有信號和槽的普通C++代碼——測試狀態機具有如上所述的相同實現,但使用信號和時隙將其集成到應用程序中。
帶有QQuebug的普通C++代碼使用普通C++代碼方法,但使用QT事件隊列來處理輸入和輸出事件。
這使得有可能比較信號和時隙的使用一方面和使用QQueS的影響,另一方面與普通C++實現相比,因為狀態機執行代碼在所有情況下都是相同的,只是只是不同地包裝。
測試狀態機
為了測試所有五個競爭對手,我為基本測試場景定義了圖1所示的狀態機。
Figure 1: The test state machine, as created with YAKINDU Statechart Tools.
測試狀態機是一個簡單的平面狀態機。它定義了六個狀態A到F,并循環這些狀態。定義了兩個輸入事件e1和e2,它們交替觸發狀態轉換。當狀態轉換發生時,也會執行一個簡單的操作。每個轉換操作只需向名為x的statechart變量添加10。從狀態F到a的轉換額外引發(或發出)out事件o。
Figure 2: The test state machine as an SCXML model in Qt Creator.
這個狀態機是使用支持SCXML生成的yakindustatechart工具定義的。這個SCXML可以添加到Qt項目中,并且可以在Qt Creator中進行編輯。如圖2所示,狀態機的結構與圖1中的結構相同,但有些細節,如轉換操作,在Qt Creator中不可見。YAKINDU狀態圖工具提供了更多的優勢,但我在這里不討論它們。
更重要的是,YakDu StuteCARS工具也可以生成基于C++的狀態機類的簡單的基于交換的實例。它還提供了一個選項,用信號和插槽生成支持Qt的類,因此這很方便。使用這個工具,我只需要手工使用QStateMachine實現基于狀態模式的狀態機。沒有可用于該變體的代碼生成器。盡管如此,我還是能夠節省大量的實現工作,同時只需使用一個狀態圖定義就可以為性能測試獲得語義上等價的狀態機。
所有測試用例都遵循相同的方案。當我想測量處理單個事件所花費的平均時間時,每個測試捕獲了單個狀態循環的一百萬次迭代。每個狀態循環執行訪問所有狀態和處理所有轉換和轉換操作所需的所有事件。所以,一個狀態循環開始和結束,狀態a處于活動狀態。這意味著,對于每個測試用例,將處理600萬個in事件和transition操作,以及一百萬個out事件及其關聯的轉換操作。測試作為命令行應用程序執行,并將迭代的時間記錄為單個批處理。每個事件的時間消耗可以簡單地通過將測量的時間除以in事件和out事件的數量之和來確定。進行多次試驗,并選擇最低值的測量結果。
測試是在我的舊版(2014年年中)MacBookPro上使用優化的代碼執行的,沒有調試信息,CoreI7四核CPU為2.4GHz。當然,具體數字在不同的機器和操作系統上會有所不同。但是,這并不相關,因為我想比較一下不同的實現方法。這些相對差異在不同的硬件和操作系統平臺上是可以比較的。
讓我們看看性能數據
是的——我想幾乎所有人都會期望一個簡單的C++實現比其他的選擇要快,但是差異的大小確實令人震驚。
Figure 3: Single event processing time compared.
使用普通C++處理單個事件平均花費7納秒。使用SCXML需要33850納秒——這是一個大約4800納秒的系數,這是一個巨大的差異!為了比較,光傳播更多的10公里,而SCXML狀態機只處理一個過渡,而在平原C++狀態機中的相同的轉換只留下了太多的時間光可以超過2米。這意味著CPU周期和能量消耗的數量級非常不同。
當然,具體數字取決于機器和使用的測試程序。我稍后再討論這個話題。但讓我們先討論其他數字。前三個測試場景都包含一個完全相同的狀態轉換邏輯,它是由YAKINDU Statechart工具生成的,但是每一個都以不同的方式包裝起來。
在使用直接連接時,使用信號和插槽來處理事件平均需要72ns。因此,與實際的狀態機邏輯相比,這種機制的開銷最小為90%。在這一點上,我不想爭論使用信號和插槽會使應用程序變慢。相反,我寧愿聲明狀態機的純代碼實現非常快。
將其與第三個場景進行比較,可以很好地了解使用事件隊列所造成的性能開銷。在這個場景中,所有狀態圖事件都通過事件隊列進行路由。與73NS每個事件,它需要一個因素10,對應信號和槽,100對應普通的C++。
我們可以假設類似的開銷也適用于其他兩個場景“普通QStateMachine”和“SCXML state machine”—它們都需要活動事件隊列。因此,當假設的事件隊列開銷從每個事件的5200ns中減去后,我們得到QStateMachine框架的粗略時間消耗,即每個事件4500ns。與普通代碼方法相比,基于QStateMachine的狀態機實現是慢點。這個與普通C++代碼實現相比,是一個大約635的因素。
最后,讓我們看看SCXML解釋器。它涉及到解釋JavaScript代碼,并添加了另一個因子~7。與純代碼方法相比,基于SCXML的狀態機實現非常慢。
分層和正交狀態機呢?
到目前為止,我只分析了一個簡單的平面狀態機。但是狀態圖提供了更多的特性,兩個最重要的結構特征是層次性和正交性。那么,這些特性的使用對狀態機運行時有什么影響呢?
首先,為了度量層次結構的影響,我定義了一個要分析的狀態機的層次變體,如圖4所示。
Figure 4: Hierarchical test statechart.
它提供了與平面狀態機完全相同的行為,但是添加了一些復合狀態。保持功能相同,但只需改變結構,就可以知道結構變量意味著多少開銷(如果有的話)。
其次,為了測量正交性的影響,我以四個正交區域的形式復制了平面狀態機。它們都有完全相同的功能。因此,得到的狀態機(見圖5)所做的工作將是簡單狀態機的四倍。
Figure 5: Orthogonal test statechart.
對于概要文件,我選擇了普通C++和SCXML實現,因為它們是最快和最慢的變體。圖6中的圖表顯示了結果。非常令人鼓舞的是,在狀態圖中使用層次結構并不會對兩種實現變體產生任何可測量的性能影響。
Figure 6: Performance impact of hierarchies and orthogonality.
另一個積極的結果是使用正交性也沒有任何負面影響。相反,雖然人們可能期望至少四倍的處理時間來完成四倍的工作,但在運行時的有效增加系數2.4和3.1顯著小于4。
為什么會這樣?原因是狀態機處理有一個一般的部分,它獨立于單個狀態和事件的處理。這部分使用52%(或3.5Ns每個事件)的平原C++狀態機,相比之下28%(或9300 NS每個事件)SCXML。最后,當使用生成的C++代碼時,正交狀態與SCXML相比影響較小。
結論
普通C++比所有的替代方案都要有效得多。使用信號和插槽或Qt事件隊列是一種框架機制,可以簡化復雜狀態機應用程序的實現和維護。Qt狀態機框架需要這兩種機制。使用生成的C++代碼,你可以選擇。
在許多場景中,特別是交互場景中,即使是SCXML狀態機也足夠快,它們可以通過在運行時切換狀態圖定義使行為可配置,從而提供更大的靈活性。
總結
以上是生活随笔為你收集整理的如何保证Qt状态机的最佳性能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从C到C++过渡的3个原因
- 下一篇: 与现代传感器的接口:轮询ADC驱动程序