パフォーマンスに関する考察と提案
タイミングに関する考察
アプリケーション開発者として、レンダリングエンジンが一貫して60フレーム/秒のリフレッシュレートを達成できるように通常努力します。ハードウェアや要件によって数値は異なりますが、60 FPSは非常に一般的です。60 FPSとは、各フレーム間に約16ミリ秒の処理可能時間があることを意味し、これには描画プリミティブをグラフィックスハードウェアにアップロードするために必要な処理も含まれます。
これは、描画プリミティブをグラフィックス・ハードウェアにアップロードするのに必要な処理を含む:
- 可能な限り非同期、イベント駆動プログラミングを使用する。
- 重要な処理にはワーカースレッドを使用する。
- 手動でイベントループを回さない。
- ブロッキング関数の中で1フレームあたり数ミリ秒以上使わない。
これを怠ると、フレームがスキップされ、ユーザー・エクスペリエンスに劇的な影響を与えます。
注意: QMLから呼び出されるC++のコードブロック内でブロッキングを回避するために、QEventLoop を自作したり、QCoreApplication::processEvents()を呼び出したりすることは、 魅力的ではありますが、決して使ってはいけないパターンです。なぜなら、シグナルハンドラやバインディングでイベントループが発生すると、QMLエンジンは他のバインディングやアニメーション、トランジションなどを実行し続けるからです。これらのバインディングは副作用を引き起こし、例えばイベントループを含む階層を破壊してしまう可能性があります。
プロファイリング
最も重要なヒントは QML ProfilerQt Creatorを使うことです。アプリケーションのどこに時間が費やされているかを知ることで、潜在的に存在する問題領域ではなく、実際に存在する問題領域に焦点を当てることができるようになります。詳しくはQt Creator: Profiler QML Applicationsを参照してください。
どのバインディングが最も頻繁に実行されているのか、あるいはアプリケーションがどの関数に最も多くの時間を費やしているのかを把握することで、問題箇所を最適化する必要があるのか、あるいはパフォーマンスを向上させるためにアプリケーションの実装の詳細を再設計する必要があるのかを判断することができます。プロファイリングを行わずにコードを最適化しようとすると、大幅なパフォーマンス改善ではなく、ごくわずかな改善にとどまる可能性が高いです。
JavaScriptコード
ほとんどのQMLアプリケーションには、プロパティバインディング式や関数、シグナルハンドラなどのJavaScriptコードが含まれています。これは一般的には問題ではありません。Qt Quick コンパイラのような先進的なツールのおかげで、単純な関数やバインディングは非常に高速に処理することができます。ただし、不要な処理が誤ってトリガーされないように注意する必要があります。JavaScriptの QML Profilerは、JavaScriptの実行とそのトリガーとなったものについての詳細な情報を表示することができます。
型変換
JavaScriptを使用する際の大きなコストとして、QML型のプロパティにアクセスする際に、C++のデータ(またはその参照)を含む外部リソースを持つJavaScriptオブジェクトが生成されるケースがあります。たいていの場合、この処理にかかる費用はかなり少なくて済みますが、かなり高額になる場合もあります。大きく複雑な値型や シーケンス型を扱う場合は注意が必要です。これらの型を変更したり、別のプロパティに代入したりするたびに、QMLエンジンはこれらの型をコピーしなければなりません。これがボトルネックになるような場合には、代わりにオブジェクト型の使用を検討してください。オブジェクト型のリストはQQmlListProperty を使って実装されているため、 値型のリストと同じような問題はありません。
単純な値型間の変換のほとんどは安価です。しかし、例外もあります。文字列から urlを作成する場合、QUrl インスタンスを構築する必要があり、コストがかかります。
プロパティの解決
プロパティの解決には時間がかかります。ルックアップは通常、その後の実行でより高速に実行されるように最適化されていますが、可能であれば、不要な作業を完全に行わないようにするのが常に最善です。
次の例では、頻繁に実行されるコードのブロックがあり(この場合は明示的なループの内容ですが、一般的に評価されるバインディング式などでもかまいません)、その中で "rect "idとその "color "プロパティを持つオブジェクトを複数回解決しています:
// bad.qml import QtQuick Item { width: 400 height: 200 Rectangle { id: rect anchors.fill: parent color: "blue" } function printValue(which: string, value: real) { console.log(which + " = " + value); } Component.onCompleted: { var t0 = new Date(); for (var i = 0; i < 1000; ++i) { printValue("red", rect.color.r); printValue("green", rect.color.g); printValue("blue", rect.color.b); printValue("alpha", rect.color.a); } var t1 = new Date(); console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations"); } }
rect.color が取得されるたびに、QMLエンジンは次のことをしなければなりません:
これを4回もやる必要はない。代わりに、ブロックの中で1回だけ共通ベースを解決すればいい:
// good.qml import QtQuick Item { width: 400 height: 200 Rectangle { id: rect anchors.fill: parent color: "blue" } function printValue(which: string, value: real) { console.log(which + " = " + value); } Component.onCompleted: { var t0 = new Date(); for (var i = 0; i < 1000; ++i) { var rectColor = rect.color; // resolve the common base. printValue("red", rectColor.r); printValue("green", rectColor.g); printValue("blue", rectColor.b); printValue("alpha", rectColor.a); } var t1 = new Date(); console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations"); } }
この単純な変更だけで、パフォーマンスが大幅に向上する。上記のコードは、以下のようにプロパティの解決をループの外に出すことで、さらに改善できることに注意してください(ループ処理中に検索されるプロパティが変更されることがないため):
// better.qml import QtQuick Item { width: 400 height: 200 Rectangle { id: rect anchors.fill: parent color: "blue" } function printValue(which: string, value: real) { console.log(which + " = " + value); } Component.onCompleted: { var t0 = new Date(); var rectColor = rect.color; // resolve the common base outside the tight loop. for (var i = 0; i < 1000; ++i) { printValue("red", rectColor.r); printValue("green", rectColor.g); printValue("blue", rectColor.b); printValue("alpha", rectColor.a); } var t1 = new Date(); console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations"); } }
プロパティ・バインディング
プロパティ・バインディング式は、参照するプロパティが変更されると再評価されます。そのため、バインディング式はできるだけシンプルに保つ必要があります。
ループの中で何らかの処理を行うが、その処理の最終結果だけが重要である場合、蓄積の中間段階でバインディング式の再評価がトリガされないようにするために、プロパティ自体をインクリメンタルに更新するのではなく、その後に更新する必要があるプロパティに割り当てる一時的なアキュムレータを更新する方がよい場合がよくあります。
次の作為的な例は、この点を説明しています:
// bad.qml import QtQuick Item { id: root width: 200 height: 200 property int accumulatedValue: 0 Text { anchors.fill: parent text: root.accumulatedValue.toString() onTextChanged: console.log("text binding re-evaluated") } Component.onCompleted: { var someData = [ 1, 2, 3, 4, 5, 20 ]; for (var i = 0; i < someData.length; ++i) { accumulatedValue = accumulatedValue + someData[i]; } } }
onCompletedハンドラのループは、"text "プロパティ・バインディングを6回再評価させる(その結果、text値に依存する他のすべてのプロパティ・バインディング、およびonTextChangedシグナル・ハンドラが、毎回再評価され、毎回表示用にテキストをレイアウトする)。この場合、蓄積の最終的な値にしか関心がないので、これは明らかに不要です。
次のように書き換えることができる:
// good.qml import QtQuick Item { id: root width: 200 height: 200 property int accumulatedValue: 0 Text { anchors.fill: parent text: root.accumulatedValue.toString() onTextChanged: console.log("text binding re-evaluated") } Component.onCompleted: { var someData = [ 1, 2, 3, 4, 5, 20 ]; var temp = accumulatedValue; for (var i = 0; i < someData.length; ++i) { temp = temp + someData[i]; } accumulatedValue = temp; } }
シーケンスのヒント
前述したように、値型のシーケンスは注意して扱わなければならない。
まず、シーケンス型は2つの異なるシナリオで異なる振る舞いを示す:
- シーケンスがQObject のQ_PROPERTY である場合(これを参照シーケンスと呼ぶ)、
- 配列がQObject のQ_INVOKABLE 関数から返される場合(これをコピー配列と呼ぶ)。
参照シーケンスは、JavaScript コード内または元のオブジェクト上で変更されるたびに、QMetaObject を介して読み書きされます。最適化として、参照シーケンス(参照値型と同様)を遅延ロードすることができます。実際のコンテンツは、それらが最初に使用されるときにのみ取得されます。これは、JavaScriptからシーケンス内の要素の値を変更すると、次のような結果になることを意味します:
コピーシーケンスは、実際のシーケンスがJavaScriptオブジェクトのリソースデータに格納されているため、読み取り/変更/書き込みのサイクルが発生せず(代わりに、リソースデータが直接変更されます)、はるかに単純です。
したがって、参照シーケンスの要素への書き込みは、コピーシーケンスの要素への書き込みよりもはるかに遅くなります。実際、N要素の参照シーケンスの1つの要素に書き込むことは、N要素のコピーシーケンスをその参照シーケンスに割り当てることと同じコストです。
以下の C++ 型が存在する("Qt.example" 名前空間に事前に登録されている)と仮定します:
class SequenceTypeExample : public QQuickItem { Q_OBJECT Q_PROPERTY (QList<qreal> qrealListProperty READ qrealListProperty WRITE setQrealListProperty NOTIFY qrealListPropertyChanged) public: SequenceTypeExample() : QQuickItem() { m_list << 1.1 << 2.2 << 3.3; } ~SequenceTypeExample() {} QList<qreal> qrealListProperty() const { return m_list; } void setQrealListProperty(const QList<qreal> &list) { m_list = list; emit qrealListPropertyChanged(); } signals: void qrealListPropertyChanged(); private: QList<qreal> m_list; };
以下の例では、タイトなループで参照シーケンスの要素に書き込みを行うため、パフォーマンスが低下します:
// bad.qml import QtQuick import Qt.example SequenceTypeExample { id: root width: 200 height: 200 Component.onCompleted: { var t0 = new Date(); qrealListProperty.length = 100; for (var i = 0; i < 500; ++i) { for (var j = 0; j < 100; ++j) { qrealListProperty[j] = j; } } var t1 = new Date(); console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds"); } }
"qrealListProperty[j] = j" 式による内部ループでのQObject プロパティの読み書きが、このコードを非常に非最適なものにしています。代わりに、機能的には同等だが、はるかに高速なものがあるだろう:
// good.qml import QtQuick import Qt.example SequenceTypeExample { id: root width: 200 height: 200 Component.onCompleted: { var t0 = new Date(); var someData = [1.1, 2.2, 3.3] someData.length = 100; for (var i = 0; i < 500; ++i) { for (var j = 0; j < 100; ++j) { someData[j] = j; } qrealListProperty = someData; } var t1 = new Date(); console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds"); } }
避けるべきもう1つの一般的なパターンは、各要素を読み取り、変更し、シーケンス・プロパティに書き戻す、読み取り-変更-書き込みループです。先ほどの例と同様に、これはQObject プロパティの読み込みと書き込みをすべての繰り返しで発生させます:
// bad.qml import QtQuick import Qt.example SequenceTypeExample { id: root width: 200 height: 200 Component.onCompleted: { var t0 = new Date(); qrealListProperty.length = 100; for (var i = 0; i < 500; ++i) { for (var j = 0; j < 100; ++j) { qrealListProperty[j] = qrealListProperty[j] * 2; } } var t1 = new Date(); console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds"); } }
その代わりに、手動でシーケンスのコピーを作成し、そのコピーを変更し、その結果をプロパティに代入して戻します:
// good.qml import QtQuick import Qt.example SequenceTypeExample { id: root width: 200 height: 200 Component.onCompleted: { var t0 = new Date(); for (var i = 0; i < 500; ++i) { let data = [...qrealListProperty]; for (var j = 0; j < 100; ++j) { data[j] = data[j] * 2; } qrealListProperty = data; } var t1 = new Date(); console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds"); } }
次に、プロパティ内の要素が変更されると、プロパティの変更シグナルが発信されます。シーケンスプロパティ内の特定の要素に多くのバインディングがある場合、その要素にバインドされる動的プロパティを作成し、その動的プロパティをシーケンス要素の代わりにバインディング式のシンボルとして使用する方がよいでしょう。
これは、ほとんどのクライアントが遭遇することのない珍しいユースケースですが、万が一このようなことをすることになったときのために知っておく価値があります:
// bad.qml import QtQuick import Qt.example SequenceTypeExample { id: root property int firstBinding: qrealListProperty[1] + 10; property int secondBinding: qrealListProperty[1] + 20; property int thirdBinding: qrealListProperty[1] + 30; Component.onCompleted: { var t0 = new Date(); for (var i = 0; i < 1000; ++i) { qrealListProperty[2] = i; } var t1 = new Date(); console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds"); } }
ループの中でインデックス2の要素だけが変更されても、変更シグナルの粒度はプロパティ全体が変更されたということなので、3つのバインディングはすべて再評価されることに注意してください。そのため、中間バインディングを追加すると有益な場合があります:
// good.qml import QtQuick import Qt.example SequenceTypeExample { id: root property int intermediateBinding: qrealListProperty[1] property int firstBinding: intermediateBinding + 10; property int secondBinding: intermediateBinding + 20; property int thirdBinding: intermediateBinding + 30; Component.onCompleted: { var t0 = new Date(); for (var i = 0; i < 1000; ++i) { qrealListProperty[2] = i; } var t1 = new Date(); console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds"); } }
上記の例では、中間バインディングのみが毎回再評価され、パフォーマンスが大幅に向上します。
値型のヒント
値型プロパティ(font、color、vector3dなど)は、シーケンス型プロパティと同様のQObject プロパティを持ち、通知セマンティクスを変更します。そのため、上記のシーケンスに関するヒントは、値型プロパティにも当てはまります。通常、値型ではあまり問題になりませんが(通常、値型のサブプロパティの数はシーケンスの要素の数よりもはるかに少ないからです)、不必要に再評価されるバインディングの数が増えれば、パフォーマンスに悪影響を及ぼします。
パフォーマンスに関する一般的なヒント
言語設計に起因するJavaScriptの一般的な性能に関する考慮事項は、QMLにも当てはまります。最も顕著なのは
- 可能な限り eval() の使用を避ける。
- オブジェクトのプロパティを削除しない
共通のインターフェース要素
テキスト要素
テキストレイアウトの計算には時間がかかります。可能な限り、StyledText の代わりにPlainText を使用してください。PlainText を使えない場合(画像を埋め込む必要があったり、タグを使ってテキスト全体ではなく文字の範囲に特定の書式(太字、斜体など)を指定する必要がある場合)は、StyledText を使うべきです。
このモードは解析コストがかかるので、テキストがStyledText の可能性がある(しかしおそらくそうではない)場合にのみAutoText を使うべきです。RichText モードは使うべきではありません。StyledText は、そのわずかなコストでほとんどすべての機能を提供するからです。
画像
画像はどのようなユーザーインターフェースにも欠かせないものです。残念ながら、読み込みにかかる時間、消費するメモリの量、使用方法のために、大きな問題の原因にもなっています。
非同期ロード
画像はかなり大きいことが多いので、画像の読み込みがUIスレッドをブロックしないようにするのが賢明です。QML Image 要素の "asynchronous" プロパティをtrue に設定することで、ローカルファイルシステムからの画像の非同期読み込みが可能になります(リモート画像は常に非同期で読み込まれます)。
非同期」プロパティがtrue に設定された Image 要素は、優先順位の低いワーカースレッドで画像を読み込みます。
明示的なソースサイズ
アプリケーションが大きな画像を読み込み、小さなサイズの要素に表示する場合、"sourceSize" プロパティをレンダリングされる要素のサイズに設定し、大きな画像ではなく、小さなサイズの画像がメモリに保持されるようにします。
sourceSizeを変更すると、画像が再読み込みされるので注意してください。
実行時の合成を避ける
また、アプリケーションで合成済みの画像リソースを提供することで、実行時の合成作業を回避できることも覚えておいてください(たとえば、シャドウ効果を持つ要素を提供する場合など)。
画像のスムージングを避ける
必要な場合のみ、image.smooth 。ハードウェアによっては遅くなりますし、画像が自然な大きさで表示される場合には視覚的な効果はありません。
絵画
同じ領域を何度も描画することは避けてください。RectangleではなくItemをルートエレメントとして使用することで、背景を何度も描画することを避けることができます。
アンカーで要素を配置する
アイテムを相対的に配置するには、バインディングではなくアンカーを使用する方が効率的です。バインディングを使用してrect2をrect1に対して相対的に配置する方法を考えてみましょう:
Rectangle {
id: rect1
x: 20
width: 200; height: 200
}
Rectangle {
id: rect2
x: rect1.x
y: rect1.y + rect1.height
width: rect1.width - 20
height: 200
}これはアンカーを使った方が効率的です:
Rectangle {
id: rect1
x: 20
width: 200; height: 200
}
Rectangle {
id: rect2
height: 200
anchors.left: rect1.left
anchors.top: rect1.bottom
anchors.right: rect1.right
anchors.rightMargin: 20
}バインディングを使ったポジショニング(アンカーを使うのではなく、ビジュアル・オブジェクトの x、y、width、height プロパティにバインディング式を割り当てる)は、最大限の柔軟性が得られるものの、比較的時間がかかります。
レイアウトが動的でない場合、レイアウトを指定する最もパフォーマンスの高い方法は、x、y、width、heightプロパティの静的初期化です。アイテムの座標は常に親からの相対座標なので、親の0,0座標から固定オフセットにしたい場合は、アンカーを使用すべきではありません。次の例では、子のRectangleオブジェクトは同じ場所にありますが、表示されているアンカー・コードは、静的初期化による固定位置決めを使用するコードほどリソース効率がよくありません:
Rectangle {
width: 60
height: 60
Rectangle {
id: fixedPositioning
x: 20
y: 20
width: 20
height: 20
}
Rectangle {
id: anchorPositioning
anchors.fill: parent
anchors.margins: 20
}
}モデルとビュー
ほとんどのアプリケーションでは、少なくとも1つのモデルがビューにデータを供給します。最大のパフォーマンスを達成するために、アプリケーション開発者が知っておかなければならないセマンティクスがいくつかあります。
カスタムC++モデル
QMLのビューで使用する独自のカスタムモデルをC++で記述することが望ましい場合がよくあります。そのようなモデルの最適な実装は、そのモデルが満たすべきユースケースに大きく依存しますが、いくつかの一般的なガイドラインを以下に示します:
- 可能な限り非同期であること
- すべての処理を(優先順位の低い)ワーカースレッドで行う。
- バックエンドの処理をバッチ化し、(潜在的に遅い)I/OとIPCを最小化する。
低優先度のワーカースレッドを使用することは、GUIスレッドを飢餓状態にするリスクを最小化するために推奨されることに注意することが重要です(その結果、知覚されるパフォーマンスが悪化する可能性があります)。また、同期とロックの仕組みはパフォーマンス低下の重大な原因となる可能性があるため、不必要なロックを避けるように注意する必要があることを覚えておいてください。
リストモデル QML型
Qt Qml ModelsはListModel 型を提供しており、ListView にデータを供給することができます。これは迅速なプロトタイピングには便利ですが、大量のデータには適していません。必要な場合は適切なQAbstractItemModel 。
ワーカースレッド内でのデータ投入
ListModel 要素は、JavaScriptの(優先順位の低い)ワーカースレッドで入力することができます。開発者は、WorkerScript の中からListModel のsync() を明示的に呼び出して、メインスレッドに変更を同期させる必要があります。詳細はWorkerScript のドキュメントを参照してください。
WorkerScript 要素を使用すると、別の JavaScript エンジンが作成されることに注意してください(JavaScript エンジンはスレッド単位であるため)。その結果、メモリ使用量が増加します。しかし、複数のWorkerScript 要素はすべて同じワーカースレッドを使用するため、アプリケーションがすでにWorkerScript 要素を使用している場合、2 つ目、3 つ目の 要素を使用することによるメモリへの影響はごくわずかです。しかしその反面、追加のワーカースクリプトは並列に実行されません。
ダイナミックロールを使わない
ListModel 要素は、最適化のために、与えられたモデルの各要素内のロールのタイプが安定していることを前提としています。もし型が要素ごとに動的に変わる可能性がある場合、モデルのパフォーマンスはかなり悪化します。
そのため、動的型付けはデフォルトでは無効になっています。動的型付けを有効にするには、開発者はモデルの booleandynamicRoles プロパティーを特別に設定する必要があります(そして、それに伴うパフォーマンスの低下を被ることになります)。絶対に必要でない限り、動的型付けは使用しないことをお勧めします。
ビュー
ビューのデリゲートは、できる限りシンプルに保つべきです。デリゲートには、必要な情報を表示するのに十分な QML を記述してください。すぐに必要でない追加機能(例えば、クリックされたときに詳細な情報を表示する場合など)は、必要になるまで作成すべきではありません(遅延初期化に関する次のセクションを参照してください)。
以下のリストは、デリゲートを設計する際に注意すべき点をまとめたものです:
- デリゲート内の要素が少なければ少ないほど、より速く作成でき、ビューのスクロールも速くなります。
- 特に、デリゲート内の相対的な位置決めには、バインディングではなくアンカーを使用してください。
- デリゲート内でShaderEffect 要素を使用することは避けてください。
- デリゲートでクリッピングを有効にしないでください。
ビューのcacheBuffer プロパティを設定して、可視領域外のデリゲートの非同期作成とバッファリングを許可することができます。cacheBuffer を使用することは、非自明であり、1 フレーム内に作成される可能性が低いビューデリゲートに対して推奨されます。
cacheBuffer は、追加のデリゲートをメモリ内に保持することに留意してください。従って、cacheBuffer を利用することで得られる価値は、追加のメモリ使用量とのバランスを取る必要があります。cacheBuffer 、まれにスクロール時のフレームレートが低下することがあります。
さらにパフォーマンスを向上させるには、ビューでアイテムの再利用を有効にすることを検討してください。詳しくは、ListViewのアイテムの再利用とTableViewとTreeViewのアイテムの再利用を参照してください。
視覚効果
Qt Quickには、開発者やデザイナーが非常に魅力的なユーザーインターフェイスを作成できる機能がいくつかあります。視覚効果だけでなく、流動性や動的な遷移もアプリケーションで効果的に使用することができますが、QMLでいくつかの機能を使用する際には、パフォーマンスに影響を与える可能性があるため、いくつかの注意が必要です。
アニメーション
一般的に、プロパティをアニメーション化すると、そのプロパティを参照するバインディングが再評価されます。通常はこれが望ましいのですが、アニメーションを実行する前にバインディングを無効にしておき、アニメーションが完了したらバインディングを再割り当てする方がよい場合もあります。
アニメーション中にJavaScriptを実行することは避けてください。例えば、xプロパティのアニメーションの各フレームに対して複雑なJavaScript式を実行することは避けるべきです。
スクリプトアニメーションはメインスレッドで実行されるため、開発者は特に注意する必要があります(そのため、完了までに時間がかかりすぎると、フレームがスキップされる可能性があります)。
パーティクル
この Qt Quick Particlesモジュールを使うと、美しいパーティクル効果をユーザーインターフェイスにシームレスに統合できます。しかし、プラットフォームごとにグラフィックハードウェアの能力は異なり、Particlesモジュールは、ハードウェアが優雅にサポートできるパラメータに制限することができません。より多くのパーティクルをレンダリングしようとすればするほど(そしてそれらが大きければ大きいほど)、60 FPSでレンダリングするためにはグラフィックハードウェアの高速化が必要になります。より多くのパーティクルに影響を与えるには、より高速なCPUが必要です。したがって、ターゲットプラットフォームですべてのパーティクルエフェクトを慎重にテストし、60 FPSでレンダリングできるパーティクルの数とサイズを調整することが重要です。
パーティクルシステムは、不要なシミュレーションを避けるために、使用していないときは無効にすることができます(たとえば、非可視要素など)。
詳細については、パーティクルシステムパフォーマンスガイドを参照してください。
エレメントのライフタイムの制御
アプリケーションをシンプルなモジュール式のコンポーネントに分割し、それぞれを 1 つの QML ファイルに含めることで、アプリケーションの起動時間を短縮し、メモリ使用量をより適切に制御できるようになります。
遅延初期化
QMLエンジンは、コンポーネントの読み込みや初期化でフレームがスキップされないようにするため、いくつかのトリッキーな処理を行います。しかし、起動時間を短縮するためには、必要のない処理を行わず、必要な処理まで遅延させることに勝る方法はありません。これを実現するには、Loader 。
ローダーの使用
ローダーは、コンポーネントの動的なロードとアンロードを可能にするエレメントです。
- ローダーの「active」プロパティを使用すると、初期化を必要な時まで遅らせることができます。
- setSource()」関数のオーバーロード版を使用すると、プロパティの初期値を指定できます。
- Loaderasynchronous プロパティを true に設定すると、コンポーネントがインスタンス化される間の流動性が向上します。
未使用要素の破棄
非可視要素の子であるために不可視である要素(例えば、最初のタブが表示されている間のタブウィジェットの2番目のタブ)は、ほとんどの場合、遅延的に初期化され、使用されなくなったときに削除されるべきです。
Loader要素でロードされたアイテムは、Loaderの "source "または "sourceComponent "プロパティをリセットすることで解放できますが、他のアイテムは明示的にdestroy()を呼び出すことで解放できます。場合によっては、アイテムをアクティブなままにしておく必要がありますが、その場合は少なくとも不可視にする必要があります。
アクティブだが不可視の要素については、レンダリングのセクションを参照してください。
レンダリング
のレンダリングに使用されるシーングラフは、非常にダイナミックでアニメーションするユーザーインターフェイスを60 FPSで流れるようにレンダリングします。 Qt Quickのレンダリングに使用されるシーングラフは、非常にダイナミックでアニメーションするユーザーインターフェイスを 60 FPS で流れるようにレンダリングします。しかし、レンダリングパフォーマンスを劇的に低下させるものがいくつかあり、開発者は可能な限りこれらの落とし穴を避けるよう注意する必要があります。
クリッピング
クリッピングはデフォルトでは無効になっています。
クリッピングは視覚効果であり、最適化ではありません。レンダラーの複雑さを(軽減するのではなく)増加させます。クリッピングが有効な場合、アイテムは自身のペイントとその子のペイントをバウンディング rectangle にクリップします。このため、レンダラーが要素の描画順序を自由に並べ替えることができなくなり、シーングラフのトラバーサルが最適化されません。
デリゲート内でのクリッピングは特に良くないので、絶対に避けるべきです。
過剰描画と不可視要素
他の(不透明な)要素で完全に覆われている要素がある場合、その「visible」プロパティをfalse に設定するのが最善です。
同様に、不可視の要素(例えば、タブ・ウィジェットの2番目のタブで、1番目のタブが表示されている場合)であっても、起動時に初期化する必要がある場合(例えば、2番目のタブのインスタンス化に時間がかかりすぎて、タブがアクティブになったときにしか初期化できない場合)は、描画コストを避けるために、"visible" プロパティをfalse に設定する必要があります(ただし、先に説明したように、アクティブなままなので、アニメーションやバインディングの評価コストは発生します)。
半透明と不透明
一般的に、不透明なコンテンツは半透明よりも描画速度が速いです。その理由は、半透明のコンテンツはブレンドが必要であり、レンダラーが不透明なコンテンツをより最適化できる可能性があるからです。
半透明のピクセルが1つある画像は、ほとんどが不透明でも完全に半透明として扱われます。エッジが透明なBorderImage も同様です。
シェーダー
ShaderEffect 、Qt Quick アプリケーションに GLSL コードをインラインで配置することができます。ただし、フラグメントプログラムは、レンダリングされた形状のすべてのピクセルに対して実行する必要があることを認識することが重要です。ローエンドハードウェアに配置し、シェーダが大量のピクセルをカバーする場合、パフォーマンス低下を避けるためにフラグメントシェーダを数命令に抑える必要があります。
GLSL で書かれたシェーダは、複雑な変形や視覚効果を書くことができま すが、使用には注意が必要です。ShaderEffectSource を使うと、描画する前にシーンが FBO にプリレンダリングされます。この余分なオーバーヘッドは、かなり高くつくことがあります。
メモリ割り当てとコレクション
アプリケーションによって割り当てられるメモリの量と、そのメモリの割り当て方法は、非常に重要な考慮事項です。メモリに制約のあるデバイスでのメモリ不足の状態についての明らかな懸念は別として、ヒープ上でのメモリの割り当てはかなり計算量の多い操作であり、ある種の割り当て戦略はページ間のデータの断片化を増やす結果になりかねません。JavaScriptは自動的にガベージコレクションされる管理されたメモリヒープを使用しており、これにはいくつかの利点がありますが、重要な意味合いもあります。
QML で書かれたアプリケーションは、C++ のヒープと自動的に管理される JavaScript のヒープの両方からメモリを使用します。アプリケーション開発者は、パフォーマンスを最大化するために、それぞれの微妙な違いを認識しておく必要があります。
QMLアプリケーション開発者のためのヒント
このセクションに記載されているヒントや提案は、あくまでもガイドラインであり、すべての状況に当てはまるとは限りません。可能な限り最良の判断を下すために、経験的な指標を用いてアプリケーションのベンチマークや分析を注意深く行うようにしてください。
コンポーネントを遅延的にインスタンス化し、初期化する
アプリケーションが複数のビュー(例えば、複数のタブ)で構成されているが、一度に必要なのは1つだけである場合、遅延インスタンス化を使用することで、任意の時点で割り当てが必要なメモリ量を最小限に抑えることができます。詳しくは、遅延初期化のセクションを参照してください。
未使用オブジェクトの破棄
コンポーネントを遅延ロードしたり、JavaScript の式の中で動的にオブジェクトを作成したりする場合、自動的なガベージコレクションを待つよりも手動でdestroy() した方がよいことがよくあります。詳しくは「要素の寿命の制御」をご覧ください。
ガベージコレクタを手動で起動しない
ほとんどの場合、ガベージコレクタを手動で起動するのは賢明ではありません。その結果、フレームが飛んだり、アニメーションがぎこちなくなったりすることがあります。
ガベージコレクタを手動で起動してもよい場合もありますが(これについては次のセクションで詳しく説明します)、ほとんどの場合、ガベージコレクタの起動は不要で逆効果です。
同一の暗黙の型を複数定義することは避ける
QML要素にカスタムプロパティが定義されている場合、そのプロパティは暗黙の型となります。これについては、次の章で詳しく説明します。Component 、同じ暗黙の型が複数定義されていると、メモリが浪費されます。このような場合、通常は明示的に新しいコンポーネントを定義し、それを再利用する方がよいでしょう。このような場合、component キーワードを使用してインライン・コンポーネントを定義することを検討してください。
カスタムプロパティを定義することで、パフォーマンスの最適化(例えば、必要なバインディングや再評価されるバインディングの数を減らすなど)になったり、コンポーネントのモジュール性や保守性が向上したりすることがよくあります。このような場合、カスタムプロパティの使用が推奨される。ただし、新しい型が複数回使用される場合は、メモリを節約するために、独自のコンポーネント(インラインまたは.qmlファイル)に分割する必要があります。
既存のコンポーネントを再利用する
新しいコンポーネントを定義することを検討している場合、そのようなコンポーネントがあなたのプラットフォームのコンポーネントセットにすでに存在しないことを再確認する価値があります。そうでない場合、QMLエンジンに型データを生成・保存させることになりますが、これは本質的に既存のコンポーネントと重複しており、すでにロードされている可能性があります。
プラグマ・ライブラリ・スクリプトの代わりにシングルトン型を使う
アプリケーション全体のインスタンスデータを格納するためにプラグマ・ライブラリ・スクリプトを使用している場合は、代わりにQObject シングルトン型を使用することを検討してください。そうすることで、パフォーマンスが向上し、JavaScript のヒープメモリの使用量が減ります。
QMLアプリケーションにおけるメモリ割り当て
QMLアプリケーションのメモリ使用量は、C++ヒープ使用量とJavaScriptヒープ使用量の2つに分けられます。それぞれに割り当てられるメモリは、QML エンジンや JavaScript エンジンによって割り当てられるため、一部は避けられませんが、残りはアプリケーション開発者の判断に依存します。
C++ ヒープには
- QMLエンジンの固定的で不可避なオーバーヘッド(実装データ構造、コンテキスト情報など);
- コンポーネントごとにコンパイルされたデータと型情報(型ごとのプロパティメタデータを含む);
- オブジェクト単位の C++ データ(プロパティ値を含む)と、アプリケーションがインスタンス化するコンポーネントに応じた要素単位のメタオブジェクト階層;
- QMLインポート(ライブラリ)によって特別に割り当てられたデータ。
JavaScriptのヒープには以下が含まれます:
- JavaScript エンジン自体(組み込みの JavaScript 型を含む)の固定的で不可避なオーバーヘッド;
- JavaScriptの統合による固定的で不可避なオーバーヘッド(ロードされた型のコンストラクタ関数、関数テンプレートなど);
- JavaScriptエンジンによって実行時に生成される、型ごとのレイアウト情報やその他の内部型データ(型については以下の注を参照);
- オブジェクトごとの JavaScript データ ("var" プロパティ、JavaScript 関数とシグナルハンドラ、最適化されていないバインディング式);
- 式の評価中に割り当てられた変数。
さらに、メインスレッドで使用するために 1 つの JavaScript ヒープが割り当てられ、オプションでWorkerScript スレッドで使用するためにもう 1 つの JavaScript ヒープが割り当てられます。アプリケーションがWorkerScript 要素を使用しない場合、そのオーバーヘッドは発生しません。JavaScriptのヒープは数メガバイトのサイズになる可能性があるため、メモリに制約のあるデバイス用に書かれたアプリケーションは、WorkerScript 要素を使用しない方がよいかもしれません。
QMLエンジンもJavaScriptエンジンも、観測された型に関する型データのキャッシュを自動的に生成することに注意してください。アプリケーションによって読み込まれるすべてのコンポーネントは個別の(明示的な)型であり、QMLで独自のカスタムプロパティを定義するすべての要素(コンポーネントのインスタンス)は暗黙的な型です。カスタムプロパティを定義していない要素(コンポーネントのインスタンス)は、JavaScriptとQMLエンジンによって、それ自身の暗黙的な型ではなく、コンポーネントによって明示的に定義された型であるとみなされます。
次の例を考えてみましょう:
import QtQuick Item { id: root Rectangle { id: r0 color: "red" } Rectangle { id: r1 color: "blue" width: 50 } Rectangle { id: r2 property int customProperty: 5 } Rectangle { id: r3 property string customProperty: "hello" } Rectangle { id: r4 property string customProperty: "hello" } }
先ほどの例では、矩形r0 とr1 はカスタムプロパティを持たないため、JavaScript と QML エンジンは両者を同じ型とみなします。つまり、r0 とr1 はどちらも明示的に定義されたRectangle 型であるとみなされます。長方形r2 、r3 、r4 はそれぞれカスタムプロパティを持っており、それぞれ異なる(暗黙の)型とみなされます。r3 とr4 は、同じプロパティ情報を持っているにもかかわらず、それぞれ異なる型であるとみなされます。
r3 とr4 の両方がRectangleWithString コンポーネントのインスタンスで、そのコンポーネント定義にcustomProperty という文字列プロパティの宣言が含まれていた場合、r3 とr4 は同じ型とみなされます(つまり、これらは独自の暗黙の型を定義するのではなく、RectangleWithString 型のインスタンスとなります)。
メモリ割り当てに関する詳細な考察
メモリ割り当てや性能のトレードオフについて決定するときは常に、CPUキャッシュの性能、オペレーティングシステムのページング、JavaScriptエンジンのガベージコレクションの影響を念頭に置くことが重要です。最適な解決策を選択するためには、ベンチマークを慎重に行う必要があります。
一般的なガイドラインのセットは、アプリケーション開発者が開発しているプラットフォームの実装の詳細に関する実践的な知識と組み合わされた、コンピュータサイエンスの基礎となる原理の確かな理解に取って代わることはできません。さらに、トレードオフを決定する際に、いくら理論的に計算しても、優れたベンチマークと分析ツールのセットに取って代わることはできません。
断片化
フラグメンテーションはC++開発の問題です。アプリケーション開発者がC++型やプラグインを定義しないのであれば、このセクションは無視してもかまいません。
時間の経過とともに、アプリケーションはメモリの大部分を割り当て、そのメモリに データを書き込み、データの一部が使い終わると、メモリの一部を解放します。その結果、"空き "メモリが連続しないチャンクに配置され、他のアプリケーショ ンが使用できるようにオペレーティング・システムに戻すことができなくなります。また、「生きている」データが物理メモリの多くの異なるページにまたがっている可能性があるため、アプリケーションのキャッシュ特性やアクセス特性にも影響を及ぼします。その結果、オペレーティング・システムにスワップを強いることになり、ファイルシステムI/Oを引き起こす可能性がある。
フラグメンテーションは、プール・アロケータ(および他の連続したメモリ・アロケータ)を利用したり、オブジェクトの寿命を注意深く管理することで一度に割り当てられるメモリ量を減らしたり、定期的にキャッシュをクリーンアップして再構築したり、ガベージ・コレクションを備えたメモリ管理ランタイム(JavaScriptなど)を利用することで回避できる。
ガベージコレクション
JavaScriptはガベージコレクションを提供します。C++ ヒープとは対照的に)JavaScript ヒープに割り当てられたメモリは、JavaScript エンジンが所有します。エンジンは定期的にJavaScriptヒープ上の参照されないデータをすべて収集します。
ガベージコレクションの意味
ガベージコレクションには利点と欠点があります。それは、オブジェクトの寿命を手動で管理することがあまり重要でなくなることを意味します。しかし、それはまた、アプリケーション開発者が制御できないタイミングで、JavaScriptエンジンによって潜在的に長時間の操作が開始される可能性があることを意味します。JavaScriptのヒープ使用がアプリケーション開発者によって注意深く考慮されない限り、ガベージコレクションの頻度と持続時間はアプリケーション体験に悪影響を及ぼすかもしれません。Qt 6.8 以降、ガベージコレクタはインクリメンタルになっています。
ガベージコレクタの手動起動
QMLで書かれたアプリケーションは、(ほとんどの場合)どこかの段階でガベージコレクションを実行する必要があります。ガベージコレクションはJavaScriptエンジンのスケジュールによって自動的に起動されますが、 アプリケーション開発者がガベージコレクタを手動で起動するタイミングを決定した方が良い場合もあります (通常はそうではありませんが)。
アプリケーション開発者は、アプリケーションがいつアイドル状態になるかを最もよく理解しているはずです。QML アプリケーションが JavaScript のヒープメモリを大量に使用し、特にパフォーマンスに敏感なタスク (例えば、リストのスクロールやアニメーションなど) の最中に定期的にガベージコレクションが発生するような場合、アプリケーション開発者はアクティビティがゼロの期間中にガベージコレクタを手動で起動するのが良いでしょう。アイドル期間は、アクティビティが発生している間にガベージコレクタを呼び出すことによって生じるユーザエクスペリエンスの低下(フレームスキップ、ぎくしゃくしたアニメーションなど)にユーザが気づかないので、ガベージコレクションを実行するのに理想的です。
ガベージコレクタは、JavaScript内でgc() 。この場合、完全な、非インクリメンタルなコレクションサイクルが実行され、完了するまでに数百から千ミリ秒以上かかることがあるので、可能な限り避けるべきです。
メモリとパフォーマンスのトレードオフ
状況によっては、メモリ使用量の増加と処理時間の短縮をトレードオフにすることができる。たとえば、タイトなループで使われるシンボル検索の結果をJavaScriptの式の一時変数にキャッシュすると、その式を評価するときのパフォーマンスが大幅に向上しますが、一時変数の割り当てが必要になります。このようなトレードオフが賢明な場合もありますが(上記のケースのように、ほとんど常に賢明です)、システムのメモリ圧迫を避けるために、処理に少し時間がかかるようにしたほうがよい場合もあります。
場合によっては、メモリ使用量の増加の影響が極端になることもあります。状況によっては、想定される性能向上のためにメモリ使用量をトレードオフすることで、ページスラッシュやキャッシュスラッシュが増加し、性能が大幅に低下することがあります。ある状況においてどのソリューションが最適かを判断するためには、トレードオフの影響を注意深くベンチマークすることが常に必要です。
キャッシュ性能とメモリ時間のトレードオフに関する詳細な情報については、以下の記事を参照してください:
- Ulrich Drepperの優れた記事:Ulrich Drepperの優れた記事:"What Every Programmer Should Know About Memory", at:https://people.freebsd.org/~lstewart/articles/cpumemory.pdf.
- C++ アプリケーションの最適化に関する Agner Fog の優れたマニュアル(http://www.agner.org/optimize/)。
高速ブートとスタートアップの最適化
高速起動のためにQt Quick アプリケーションを最適化した実際の経験に基づき、以下のベストプラクティスを検討してください:
- アプリケーションを最初から高速に起動するように設計する。ユーザに最初に何を見せたいかを考えてください。
- を使いましょう。 QML Profilerを使って起動時のボトルネックを特定する。
- チェーンローディングを使いましょう。CPUのコアの数だけloaders を走らせましょう(例えば2コア:同時に2つのローダーを走らせる)。
- 最初のloader は非同期であってはならず、一部のコンテンツがすぐに表示されるようにする。非同期ローダーのトリガーはその後に行う。
- バックエンドのサービスには、必要なときだけ接続する。
- 必要なときにインポートされるQMLモジュールを作成する。遅延ロードされたモジュールやタイプを使用することで、重要でないサービスを必要に応じてアプリケーションで利用できるようにすることができます。
- optipngなどのツールを使用して、PNG/JPG画像を最適化します。
- 3Dモデルを最適化するには、頂点の量を減らし、見えない部分を削除します。
- glTFを使用して、3Dモデルの読み込みを最適化する。
- パフォーマンスに影響する可能性があるため、クリップと不透明度の使用を制限する。
- GPU の制限を測定し、UI を設計するときに考慮する。詳しくは、フレームキャプチャとパフォーマ ンスプロファイリングを参照してください。
- Qt Quick Compiler を使ってQML ファイルをプリコンパイルする。
- あなたのアーキテクチャでスタティックリンクが可能かどうか調べてください。
- 命令型のシグナルハンドラではなく、宣言型のバインディングにする。
- プロパティ・バインディングはシンプルに保つ。一般的に、QMLコードはシンプルで、楽しく、読みやすいものにしてください。そうすれば、良いパフォーマンスが得られます。
- 作成時間が問題になる場合は、複雑なコントロールを画像やシェーダーに置き換えてください。
そうしないでください:
- QMLを使いすぎる。QMLを使うとしても、すべてをQMLで行う必要はありません。
- main.cppですべてを初期化してください。
- 必要なインターフェイスをすべて含む大きなシングルトンを作ってください。
- ListView や他のビューのために複雑なデリゲートを作成する。
- どうしても必要な場合以外はクリップを使いましょう。
- Loader 、アプリケーションページのような大きなものを遅延ロードするには最適ですが、単純なものをロードするにはオーバーヘッドが多すぎます。何でもかんでも高速化する黒魔術ではありません。QMLコンテキストの追加項目なのです。
これらのプラクティスは、特に組込み機器において、秒以下の起動時間とスムーズなユーザーエクスペリエンスを実現するのに役立ちます。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.