Jika Anda mengembangkan aplikasi dengan Qt dan menggunakan mesin status, Anda mungkin menggunakan kerangka mesin status Qt. Anda dapat mendefinisikan mesin dalam C++ murni atau SCXML, atau menghasilkan kode C++ dari diagram state-chart. Artikel ini mengevaluasi opsi ini dalam hal fungsionalitas,適用abilitas, dan performa.
Sebagai pengembang perangkat lunak, Anda mungkin telah menulis banyak blok switch-case. Untuk banyak orang, ini adalah cara paling sederhana untuk mengimplementasikan mesin status ketika tidak ada alat tingkat tinggi yang tersedia. Namun, seiring bertambahnya kompleksitas, pemeliharaan menjadi tantangan, dan logika switch-case yang dibuat secara manual tidak lagi berkelanjutan.
Mengimplementasikan mesin status
Saat bekerja dengan bahasa berorientasi objek seperti C++, pola state adalah strategi yang terbukti. Ini memodelkan setiap status sebagai kelas, dan transisi sebagai kelas opsional terpisah. Sebuah mesin status dibangun dengan membuat instance dari kelas-kelas ini dan menyambungkannya bersama. Kerangka dapat secara signifikan mengurangi kode boilerplate.
API mesin status Qt memungkinkan Anda mengonfigurasi mesin dengan kode yang ringkas, menyisakan semantik eksekusi ke kerangka. Sementara ini menyederhanakan implementasi, kode sumber masih dapat tumbuh besar ketika mesin berisi puluhan atau ratusan status, membuat desain keseluruhan sulit dipahami.
Qt juga mendukung State Chart XML (SCXML), standar W3C untuk deskripsi state-chart. Menulis XML secara manual adalah hal yang melelahkan, jadi Qt Creator menawarkan editor grafis sederhana.
Terlepas dari teknik implementasi, model grafis adalah cara paling efektif untuk mengedit dan memahami mesin status. Model grafis dapat diekspresikan dalam teks melalui SCXML, atau digunakan untuk menghasilkan kode sumber dalam bahasa apa pun, termasuk switch-case C++ atau konstruksi QStateMachine. Dengan menyerahkan transformasi ke alat, Anda menghindari kesedihan kode manual dan membawa semua pendekatan ke tingkat kegunaan yang sama. Implementasi, tuttavia, tetap secara fundamental berbeda. Artikel ini berfokus pada perilaku waktu proses, terutama performa.
UnsplashPhoto by Austris Augusts on Unsplash
Pesaing
Bagaimana pendekatan berbeda dalam penggunaan CPU? Untuk menjawab ini, saya membuat suite benchmark. Bagian pertama membandingkan strategi implementasi yang berbeda. Ini adalah pesaing:
Interpreter SCXML – mesin tes didefinisikan dalam SCXML dan dieksekusi oleh kelas QSCXMLStateMachine Qt.
Pola state – mesin tes diimplementasikan menggunakan kelas QStateMachine.
Kode C++ murni – mesin tes diimplementasikan oleh kelas C++ yang menggunakan pendekatan switch-case dasar.
Catatan: Kode untuk contoh ini dapat ditemukan di sini.
Dua varian pertama menyiratkan penggunaan konsep Qt seperti signal dan slot serta penggunaan antrean peristiwa Qt, sementara implementasi C++ murni tidak memerlukan infrastruktur ini. Untuk membuat pendekatan lebih dapat dibandingkan, suite tes menyertakan dua skenario tambahan:
Kode C++ murni dengan signal dan slot – implementasi yang sama seperti di atas tetapi dihubungkan ke sistem signal/slot Qt.
Kode C++ murni dengan QEvent – pendekatan C++ murni yang merute peristiwa melalui antrean peristiwa Qt.
Ini memungkinkan kita untuk mengisolasi dampak signal/slot dan antrean peristiwa pada waktu eksekusi sambil mempertahankan logika transisi status yang identik.
Mesin status tes
Untuk semua lima varian saya menggunakan mesin status yang diilustrasikan dalam Gambar 1 untuk skenario tes dasar.
Gambar 1: Mesin status tes, yang dibuat dengan YAKINDU Statechart Tools. (Sumber: Penulis)
Mesin memiliki enam status datar A–F, melintasi mereka, dan bereaksi terhadap dua peristiwa input e1 dan e2 yang secara bergantian memicu transisi. Setiap tindakan transisi menambahkan 10 ke variabel x; transisi dari F ke A juga memancarkan peristiwa output o.
Gambar 2: Mesin status tes sebagai model SCXML di Qt Creator. (Sumber: Penulis)
Saya mendefinisikan mesin dengan YAKINDU Statechart Tools, yang dapat menghasilkan baik SCXML maupun kode C++ switch-case murni. Kode yang dihasilkan juga mendukung signal dan slot Qt. Saya mengimplementasikan varian QStateMachine secara manual, tetapi semua implementasi lainnya berasal dari definisi state-chart yang sama.
Setiap tes berjalan satu juta iterasi dari satu loop status. Sebuah loop dimulai dan berakhir dengan status A aktif, dan memproses 6 juta peristiwa input, 1 juta peristiwa output, dan tindakan transisi yang terkait. Tes dieksekusi sebagai aplikasi command-line, dan waktu untuk seluruh batch dicatat. Waktu per peristiwa diperoleh dengan membagi waktu total dengan jumlah peristiwa yang diproses. Nilai terendah dari berulang kali digunakan.
Benchmark dilakukan pada MacBook Pro 2014 dengan quad-core i7 2,4 GHz dalam build release yang dioptimalkan. Angka pasti akan bervariasi pada perangkat keras lain, tetapi perbedaan relatif tetap konsisten.
Angka performa
Memproses satu peristiwa dengan C++ murni memakan waktu rata-rata 7 ns, sementara SCXML membutuhkan 33.850 ns – faktor sekitar 4.800. Menggunakan signal dan slot menambahkan sekitar 72 ns per peristiwa (~90% overhead). Merute melalui antrean peristiwa Qt menambahkan sekitar 731 ns per peristiwa (~10× signal, ~100× C++ murni). Setelah mengurangi overhead antrean, kerangka QStateMachine memakan waktu sekitar 4.500 ns per peristiwa – sekitar 635× lebih lambat dari C++ murni. Interpreter SCXML, yang menafsirkan JavaScript, menambahkan faktor ~7× lagi, menjadikannya varian paling lambat.
Gambar 3: Perbandingan waktu pemrosesan peristiwa tunggal. (Sumber: Penulis)
Dampak hierarki dan ortogonalitas
Saya juga memprofil varian hierarkis dan ortogonal dari mesin yang sama. Mesin hierarkis (Gambar 4) dan mesin ortogonal (Gambar 5) memiliki perilaku yang sama tetapi struktur yang berbeda.
Gambar 4: Statechart tes hierarkis. (Sumber: Penulis)
Gambar 5: Statechart tes ortogonal. (Sumber: Penulis)
Hasil, yang ditunjukkan dalam Gambar 6, menunjukkan bahwa menambahkan hierarki tidak menimbulkan overhead yang terukur pada implementasi apa pun. Ortogonalitas juga tidak menggandakan waktu proses sebagaimana mungkin diharapkan; kenaikan sekitar 2,4× untuk C++ murni dan 3,1× untuk SCXML, jauh di bawah biaya empat kali teoretis.
Gambar 6: Dampak performa hierarki dan ortogonalitas. (Sumber: Penulis)
Kesimpulan
C++ murni memberikan performa terbaik. Signal, slot, dan antrean peristiwa Qt menukar kecepatan untuk kemudahan dan kemudahan pemeliharaan, sementara kerangka QStateMachine adalah yang paling lambat dari tiga varian inti. SCXML sering cukup cepat untuk aplikasi interaktif dan menawarkan keuntungan rekonfigurasi waktu proses. Ketika kecepatan maksimum kritis, kode switch-case yang dibuat secara manual adalah pendekatan yang disarankan.