本文へスキップします。

本文へ

【全Qt】
【全・Qt】SRAロゴ
H1

QML の性能を測定結果から読み解く — qmlprofileranalyzer の活用

技術記事

(掲載 2026年10月23日/更新 2026年10月24日)

QML の性能解析

QML アプリケーションで「画面が表示されるまで時間がかかる」「操作中に一瞬引っかかる」といった問題が起きたとき、まず必要なのは、どの処理に時間がかかっているかを測定することです。

Qt Creator の QML Profiler では、QML オブジェクトの生成、バインディングの評価、JavaScript の実行などをタイムラインや統計で確認できます。イベントとソースコードを対応付けながら、性能上の問題を調べられます。

調査を進めると、測定結果をテキストで保存したり、変更前後の結果を比較したり、重い処理とその理由を報告書に整理したくなることがあります。 この記事では、保存済みの QML Profiler トレースをコマンドラインで解析する qmlprofileranalyzer を紹介します。基本的な使い方に加えて、解析結果をどう読み、次の調査につなげるかを説明します。

初めて使う方は準備から ranges まで、測定値の読み方を知りたい方は時計デモの実測例から読めます。変更後の検証はcompare以降を参照してください。

qmlprofileranalyzer でできること

qmlprofileranalyzer は、QML Profiler のトレースファイルを読み取り、統計やイベントの情報をテキストで出力するツールです。Qt Creator の画面で確認する作業と組み合わせて利用できます。 対応する入力形式は、XML 形式の .qtd と、Qt Creator が扱うバイナリ形式の .qzt です。

まず覚えるコマンドは、次の3つです。

コマンド 調べること
summary記録時間、イベント数、最長の処理など、トレース全体の概要
hotspots自己時間の合計や呼び出し回数から見た、重い処理の候補
ranges個々の処理区間の親子関係と、子の処理を除いた時間

変更前後を比較するときには compare を使います。このほか、コード位置を確認する types、生のイベントを表示する events、形式を相互変換する convert などがあります。 解析結果の出力はテキストです。現在の実装には、GUI や JSON・CSV 出力はありません。

準備と最初の実行

ソースコードは、qt-tools リポジトリの qmlprofileranalyzer ディレクトリにあります。 ビルドには CMake、C++17 に対応するコンパイラ、Qt が必要です。現行のビルド設定では Qt 6.8 以降を要求し、Core、Gui、Qml、Quick、Test、Xml の各モジュールを参照しています。

リポジトリを初めて取得する場合は、Git を用意し、次のように取得して作業ディレクトリへ移動します。ツール本体、時計デモ、採取スクリプト、固定サンプルは、このリポジトリに含まれています。

git clone https://github.com/Kenji-Sugita/qt-tools.git
cd qt-tools/qmlprofileranalyzer

本記事で使用したファイルは、公開リポジトリの 5aca1d5be51e75ba4067aec4f2229dd7a9f42674 時点の内容と一致することを確認しています。同じ版で試す場合は、新しく取得したリポジトリで git checkout 5aca1d5be51e75ba4067aec4f2229dd7a9f42674 を実行してからビルドしてください。

本記事では、macOS と Qt 6.12.0 の環境で動作を確認しました。 以下を qmlprofileranalyzer ディレクトリで実行します。Qt のインストール先は環境に合わせて変更してください。

/usr/local/qt/Qt/6.12.0/macos/bin/qt-cmake -S . -B build

cmake --build build

ctest --test-dir build --output-on-failure

qt-cmake は、使用するQtの bin ディレクトリにあるCMakeのラッパーです。そのQtの設定を渡すため、上の手順では CMAKE_PREFIX_PATH の指定が不要です。従来の cmake -S . -B build -DCMAKE_PREFIX_PATH=... でも構いません。Qtや構成方法を切り替えて試すときは、新しいビルドディレクトリを使ってください。

まずは同梱の最小サンプルを読み込んでみます。

./build/qmlprofileranalyzer summary samples/minimal.qtd

これは読み込み確認用の固定サンプルです。自分のアプリケーションを調べるときは、QML Profiler で対象の操作を記録し、保存したトレースファイルを指定します。 以下の説明では、そのファイル名を trace.qzt とします。

今回の確認では、既存サンプルに加え、Qt 6.12.0 でビルドした同梱デモから、同バージョンの qmlprofiler で新しいトレースを採取し、概要表示、ホットスポット集計、処理区間の解析ができることも確認しています。

同梱デモからトレースを採取する

手元にトレースがない場合は、同梱の時計デモで採取から解析まで試せます。前節のビルド後、同じディレクトリで次を実行してください。build/clock-qt612.qtd は新しい出力先として指定し、既存の測定を残したい場合は別の名前を使います。

QMLPROFILER_BIN=/usr/local/qt/Qt/6.12.0/macos/bin/qmlprofiler \
DEMO_BIN="$PWD/build/localizedclockdemo" \
PORT=39683 \
bash scripts/generate_executable_demo_trace.sh "$PWD/build/clock-qt612.qtd"

./build/qmlprofileranalyzer summary build/clock-qt612.qtd
./build/qmlprofileranalyzer hotspots build/clock-qt612.qtd
./build/qmlprofileranalyzer ranges build/clock-qt612.qtd

このスクリプトは、デモを offscreen プラットフォームで起動し、ローカルの指定ポートに qmlprofiler を接続します。画面は表示せず、デモは約15秒で自動終了します。終了後に Trace written to: と保存先が表示されることを確認してください。指定ポートが使用中の場合は、PORT を空いている番号に変更します。

採取スクリプトの既定パスは古い Qt を指すため、ここでは QMLPROFILER_BIN を明示しています。デモと採取ツールの両方で Qt 6.12.0 を使います。 この手順は解析の練習用です。実際の画面表示や操作の遅さを調べる際は、対象アプリケーションを実際の表示環境で測定してください。

以降の例にある trace.qzt は、解析対象のトレースファイル名です。同梱デモで続けて試す場合は、採取した build/clock-qt612.qtd に置き換えて実行してください。.qtd のまま解析できるため、.qzt への変換は不要です。

まず全体を確認する:summary

./build/qmlprofileranalyzer summary trace.qzt

時計デモで採取したトレースの実際の出力から、連続する8行を抜粋します。以下の実測例も同じトレースを使用しています。

traceDuration: 15498283333
totalTime: 897585502
eventTypes: 37
events: 448
ranges: 106
pointEvents: 342
notes: 0
longestRange: idx=4 type=Main.qml:0 duration=635398000

traceDuration の15,498,283,333 nsは約15.50秒です。一方、totalTime はトレースに記録された別の集計値で、経過時間とは区別します。イベント448件のうち、処理区間が106件、点イベントが342件であることも読み取れます。

summary では、主に次の項目を確認します。

  • traceDuration:トレースの終了時刻と開始時刻の差
  • eventTypes:イベント型の数
  • events:記録されたイベントの数
  • ranges:継続時間を持つイベントの数
  • pointEvents:継続時間を持たないイベントの数
  • longestRange:最も長い処理区間

ここでは、意図した操作を記録できているか、記録量が極端に少なくないか、どの処理が長いかを確認します。 注意したいのは、型ごとの totalDuration に、親子関係にある処理の重複が含まれることです。

たとえば、ある処理が10 msかかり、その内部で子の処理が8 msかかったとします。両方の時間を足すと18 msになりますが、これを「全体で18 msかかった」と読むことはできません。 summary で大きな値を見つけたら、次に hotspots と ranges で中身を確認します。

重い処理を絞る:hotspots

./build/qmlprofileranalyzer hotspots trace.qzt

hotspots は、同じコード位置などに対応する処理を集約して表示します。調査の中心になるコマンドです。 特に注目するのは、次の項目です。

  • selfTotal:子の処理区間を除いた自己時間の合計
  • inclusiveTotal:子の処理を含む時間の合計
  • calls:呼び出し回数
  • medianSelf:1回あたりの自己時間の中央値
  • maxSelf:1回あたりの自己時間の最大値

同梱の最小サンプルでは、次のコマンドを実行できます。

./build/qmlprofileranalyzer hotspots samples/minimal.qtd

出力から主要な項目を抜き出し、読みやすく改行すると、次のようになります。

calls=1
selfTotal=40
inclusiveTotal=40
medianSelf=40
maxSelf=40
file=samples/qml/AutoTrace.qml:17

時間の数値はナノ秒単位です。1,000,000 ns が1 msに相当します。このサンプルの値は形式確認用であり、実際のアプリケーションの性能を示すものではありません。 実際の解析では、selfTotal だけでなく、calls、medianSelf、maxSelf を組み合わせて読みます。

現行実装の中央値:medianSelf は、値を昇順に並べたときの中央の値です。ただし、偶数件では中央2値の平均ではなく、大きい側の値を返します。たとえば [10, 20] なら 20 です。2件しかない場合は最大値と一致するため、少数の測定を解釈するときは calls、minSelf、maxSelf も確認します。

たとえば、自己時間の合計が大きくても、1回の処理は短く、呼び出し回数が非常に多い場合があります。その場合は、処理内容を高速化するだけでなく、不要な再評価や更新が起きていないかを調べることが有効です。 一方、中央値は小さいのに最大値だけが大きい場合は、初回処理や特定の操作時だけ発生する負荷が候補になります。

「1回が重いのか」「回数が多いのか」「一部の実行だけが重いのか」を分けることで、ソースコードで確認する場所を絞れます。

親子関係を確認する:ranges

./build/qmlprofileranalyzer ranges trace.qzt

ranges では、集約前の個々の処理区間を確認できます。

  • duration:処理区間全体の時間
  • childTime:子の処理区間が覆う時間
  • selfTime:子の処理区間を除いた時間
  • depth:親子関係の深さ
  • parent:親の処理区間への参照。親がある場合に表示

たとえば、親の処理区間が10 ms、その中の子の処理区間が8 msなら、親の自己時間は2 msになります。

親の処理区間:10 ms
  └─ 子の処理区間:8 ms

親の自己時間:2 ms

この関係を確認すると、親の処理そのものが重いのか、その内部の処理に時間を使っているのかを区別しやすくなります。 実装では、子の区間どうしが重なる場合、その重なりを二重に差し引かないよう、子の区間が覆う時間を計算しています。

自己時間の読み方:selfTime は、記録された子の処理区間で説明されない時間です。その値だけから、特定の QML 式や C++ 関数の CPU 実行時間を断定することはできません。対応するコードや Qt Creator のタイムラインも確認します。

補助情報は、調査の手掛かりとして使う

hotspots と ranges には、追加調査の候補を見つけるための情報があります。

  • outlier:一部の実行だけが長くなっていないか
  • burst:短い時間に呼び出しが集中していないか
  • image:画像ロードと時間的に重なる処理がないか
  • compilingNote:Compiling の時間を解釈するときに注意が必要か
  • gapWarning:記録された QML の子の処理区間では説明しにくい時間がないか

これらは、実装上の条件に基づく候補表示です。たとえば、image=yes は画像が遅さの原因であることを証明するものではありません。画像ロードと重なる処理を見つけたら、画像サイズ、読み込み方法、表示のタイミングなどを確認します。

gapWarning=yes も同様です。QML の子の処理区間では説明しにくい時間があることを示すもので、その時間が C++ 処理なのか、I/O なのか、別の処理なのかは追加調査が必要です。 Qt Creator の公式ドキュメントでも、QML Profiler は C++ コード自体をプロファイルしないため、C++ 側の詳細な解析には汎用のプロファイラーなどを使うよう説明しています。

実測例:時計デモの調査対象を選ぶ

前述の時計デモを macOS / Qt 6.12.0、offscreen で1回測定した結果を使い、調査対象を選んでみます。記録時間は約15.50秒、イベント数は448件、うち処理区間は106件でした。以下はその実測値の抜粋であり、性能の基準値や改善後の結果ではありません。環境や初期化状態によって値は変わります。

公開リポジトリから再現できるのは、同じデモを使った採取・解析の手順です。固定サンプルの解析値は同じ版で確認できますが、新しく採取したトレースの実測値は一致するとは限りません。本記事の実測値の元トレースはリポジトリには収録していないため、読者の環境で新しく採取してください。

hotspots と types の情報から、主な3項目を整理しました。時間は読みやすいように ns から ms に換算し、小数第3位に丸めています。コード位置は同梱の demos/localizedclock/Main.qml に対応します。

種類・コード位置 calls selfTotal(ms) inclusiveTotal(ms)
Compiling / Main.qml:01635.398635.398
Creating / Main.qml:432192.508192.509
Javascript / Main.qml:56154.47066.412

起動時の負荷を調べるなら、まず635.398 msの Compiling が候補になります。ただし、Main.qml:0 は特定の QML 式を指す行番号ではありません。compilingNote=yes も付いているため、QML ファイルだけが原因と決めず、import やプラグインのロードを含め、再測定で初回処理の傾向を確認します。

次の Main.qml:43 は、ロケール名を表示する Text の生成に対応します。自己時間は2件で、最小値が1,666 ns、最大値が192,506,708 nsでした。2件に大きな差があり、現行実装では medianSelf が最大値と一致します。outlier=no でも「ばらつきが小さい」とは読めません。また、QML の生成処理は複数段階で記録されるため、calls=2 をそのまま「Text を2個作った」と解釈しないことも大切です。

この位置で記録された負荷が、テキスト関連の初期化などに由来する可能性はありますが、この数値だけでは原因を特定できません。次は ranges で該当区間と子の処理を確認し、Qt Creator のタイムラインやログと照合します。

一方、Main.qml:56 は毎秒時刻を更新する Timer の onTriggered に対応します。15回の自己時間合計は4.470 msですが、子の処理を含む合計は66.412 msです。ここで「ハンドラー自身が66.412 ms使った」と判断すると、調査対象を誤る可能性があります。

このJavaScript処理の hotspots 出力から該当項目を抜粋します。長いパスや他の項目を省略し、項目ごとに改行しています。数値は元のns単位のままです。

idx=32
type=Main.qml:56
calls=15
selfTotal=4469876
inclusiveTotal=66412293

対応するコードは次の部分です。

onTriggered: {
    const now = new Date();
    const locale = Qt.locale();
    root.time = now.toLocaleTimeString(locale, Locale.ShortFormat);
    root.date = now.toLocaleDateString(locale);
    root.seconds = now.getSeconds();
}

プロパティの更新に伴うバインディングなどが調査候補になります。ranges で、このハンドラーに対応する区間の id と、その id を parent に持つ子の区間をたどり、types でコード位置を確認します。 繰り返し操作の負荷を調べるなら、起動時の大きな値だけに注目せず、このような継続して実行される処理も確認します。

この例で分かるのは、起動時と定期更新で調査対象が異なること、そして自己時間・子を含む時間・回数を合わせて読む必要があることです。原因の確定や改善効果の判断には、対応するコードを調べ、条件をそろえて再測定する作業が必要です。

Qt Creatorの統計表示と照合する

同じトレースをQt Creatorの「Analyze → QML プロファイラオプション → QML トレースを読み込む」で開き、「統計」を選びます。次は、この記事の時計デモのトレースを実際に読み込んで照合した結果です。トレース全体を対象にし、ファイル・行番号に加えてイベントの種類も合わせます。

時計デモのトレースをQt Creatorで開いた統計画面。JavaScriptの行は合計66.4 ms、自己時間4.47 ms、呼び出し回数15回。
図:同じトレースをQt Creatorで表示した統計画面(クリックで原寸表示)。パスと型の列は表示幅のため省略されています。以下の表と、合計時間・Self Time・呼び出し回数を照合してください。
対象Qt Creatorの表示CLI出力との対応
Compiling / Main.qml:0合計635 ms、自己635 ms、1回635,398,000 nsをms表示に丸めた値
Creating / Main.qml:43合計193 ms、自己193 ms、2回約192.509 ms/192.508 msの表示上の丸め
JavaScript / Main.qml:56合計66.4 ms、自己4.47 ms、15回inclusiveTotal/selfTotal/callsと対応

特に Main.qml:56 には「ハンドリング中のシグナル」と「JavaScript」の別々の行があります。行番号だけで比較せず、上の例ではJavaScriptの行を選びます。合計66.4 msに対して自己時間は4.47 msなので、内部の処理も調査対象になります。Qt Creatorではタイムラインで該当区間を確認し、CLIでは ranges の親子関係をたどります。

Qt Creatorは時間的位置やコードを画面で確認しやすく、CLIは項目をテキストに残して比較しやすい、という使い分けができます。表示単位・丸め・集計範囲を合わせて照合し、すべての統計項目が同じ定義であるとは仮定しません。

変更前後を比較する:compare

性能改善のためにコードを変更したら、同じ操作を再度測定します。

./build/qmlprofileranalyzer compare before.qzt after.qzt

compare は、2つのトレースの hotspots を突き合わせ、自己時間や呼び出し回数の差を表示します。

  • status:追加(added)、消失(removed)、変化(changed)、変化なし(unchanged)の分類
  • selfDelta:変更後の自己時間合計 − 変更前の自己時間合計
  • selfDeltaPct:自己時間合計の変化率
  • callsDelta:呼び出し回数の差
  • medianSelfDelta:自己時間中央値の差
  • maxSelfDelta:自己時間最大値の差

selfDelta が負なら、対応する処理の自己時間合計は減っています。ただし、それだけで改善に成功したと判断するのではなく、呼び出し回数や測定した操作も確認します。 同じ操作を完了しながら不要な呼び出しが減ったのか、単に記録時間が短くなったのかでは意味が異なります。

比較には、トレースごとに変わり得るイベント番号ではなく、groupKey を使います。このキーは、主にイベントの種類、ファイル名、行番号、列番号から作られます。 そのため、ソースコードの編集で行番号が変わったり、記録されるファイルパスが変わったりすると、同じ役割の処理でも別の項目として扱われる場合があります。added や removed が出たときは、コード位置の変化も確認してください。

現行の compare はホットスポットの差分比較です。処理区間の親子関係の差分や、統計的な有意差までは判定しません。差が大きい項目を見つけたら、それぞれのトレースの ranges で詳細を調べます。

初回表示と再表示を分けて測定する

画面の初回表示と、同じ画面をもう一度表示した場合とでは、実行される処理が異なることがあります。 初回だけ大きな負荷がある場合と、再表示でも毎回同じ負荷がある場合では、改善の方針も変わります。次のように分けて記録すると、調査しやすくなります。

  1. アプリケーション起動後の初回表示を記録する。
  2. 同じ操作で再表示したときのトレースを記録する。
  3. 必要に応じて再表示を複数回測定し、ばらつきを確認する。
  4. compare で差を調べ、hotspots と ranges で内容を確認する。

比較する際は、端末、Qt のバージョン、ビルド設定、データ量、操作内容、記録区間をそろえます。コード変更の効果を調べる場合も、変更対象以外の条件をできるだけ維持します。

初回の Compiling が大きい場合は、QML の初回処理が候補になります。ただし、ツールの注意情報にもあるように、import やプラグインのロードが関係する可能性があるため、値だけから原因を決めないようにします。

解析結果を記録し、ソースコードと結び付ける

テキスト出力は、調査記録として保存できます。

./build/qmlprofileranalyzer summary trace.qzt > summary.txt
./build/qmlprofileranalyzer hotspots trace.qzt > hotspots.txt
./build/qmlprofileranalyzer ranges trace.qzt > ranges.txt
./build/qmlprofileranalyzer types trace.qzt > types.txt

types では、イベントの種類や、ファイル名・行番号などを確認できます。測定時のソースコードと対応付けることで、「どの処理を、どの理由で調べるのか」を具体化できます。 記録には、トレースだけでなく、測定時のコードのリビジョン、ビルド条件、実行した操作も残しておくと、後から再確認しやすくなります。

AI に解析の補助を依頼する場合も、出力の数値だけでなく、対応するコードと測定条件を渡します。そのうえで、測定から確認できる事実、原因の仮説、次に必要な確認を分けて整理すると、改善案を検証しやすくなります。

qmlprofileranalyzer を使うと、保存済みのトレースから、調査に必要な数値とコード位置を繰り返し取り出せます。重い処理を見つけ、その理由を調べ、変更後に同じ条件で測り直す。この流れの中で活用してください。

性能改善を相談するときに用意するとよい情報

QML の画面表示や操作の応答性に課題があり、測定方法や原因の切り分け、改善方針にお困りの場合は、SRA にご相談ください。相談時に次の情報を共有いただくと、具体的な検討を進めやすくなります。

  • 使用中の Qt のバージョン、対象 OS、端末・機器
  • 遅いと感じる画面や操作、再現手順
  • ビルド設定、データ量、初回表示・再表示などの測定条件
  • 測定結果と、これまでに確認・変更した内容
  • 現在の処理時間と、目標とする応答時間

最初からすべてを調べ切る必要はありません。分かる範囲で共有し、未測定の項目は未測定としてお伝えください。

支援内容は、SRA の Qt コンサルティングをご覧ください。

参考資料