os_signpostとxctraceで重要経路の性能ゲートを構築する

CI/CD ·約 10 分

os_signpostとxctraceで重要経路の性能ゲートを構築する

ローカルの Instruments では「初期表示まで約1秒」だったのに、マージ後のバージョンをリモート環境で測ると明らかに遅くなることがあります。多くの場合、問題はツールではなく、計測対象の境界が曖昧なことにあります。ネットワーク待機、JSON の解析、データベースへの書き込み、UI への反映を1つの所要時間にまとめると、マシンの状態が少し変わるだけで結果を比較できなくなります。より信頼性の高い方法は、os_signpost で処理区間を明示し、クラウド Mac 上の xctrace で同じシナリオを繰り返し記録することです。

再現可能な重要経路を先に定義する

最初から「アプリ全体がどれだけ速いか」を問うのではなく、開始点と終了点が安定しているユーザー操作を1つ選びます。たとえば注文画面を開いた後、ローカルキャッシュの読み込み開始から、最初の一覧セルへのデータバインド完了までを対象にします。これを次の3つの区間に分割します。

区間 開始点 終了点 ゲートへの適性
load_cache キャッシュ読み込みの開始 構造化データの返却 適している
decode_payload デコードの開始 ドメインオブジェクトの生成 適している
render_first_batch 最初のモデル群を反映 最初のビュー群のレイアウト完了 適している
wait_remote_api 外部リクエストの開始 レスポンスの受信 診断専用

外部ネットワークの所要時間は経路やサーバーの状態に左右されるため、マージを直接ブロックする基準には向きません。ゲートでは、プロセス内で完結し、入力が固定され、繰り返し実行できる処理を優先すべきです。

1つの区間で答える問いは1つだけにします。同じ計測区間にネットワーク、ディスク、レンダリングが含まれていると、上限を超えても、どこを修正すべきか判断できません。

安定して関連付けられる計測点を追加する

subsystem と category は固定し、区間名にもユーザーデータを連結しないでください。並行処理を計測する場合は、業務処理ごとに独立した OSSignpostID を作成し、隣接する2つのタスクで開始点と終了点が誤って対応付けられないようにします。

import os

enum PerfTrace {
    static let log = OSLog(
        subsystem: "com.example.mobile",
        category: "CriticalPath"
    )

    static func begin(_ name: StaticString) -> OSSignpostID {
        let id = OSSignpostID(log: log)
        os_signpost(.begin, log: log, name: name, signpostID: id)
        return id
    }

    static func end(_ name: StaticString, id: OSSignpostID) {
        os_signpost(.end, log: log, name: name, signpostID: id)
    }
}

呼び出し側では必ず defer で区間を閉じてください。そうしないと、例外経路で開始点だけが記録され、終了点のない区間が残ります。

let traceID = PerfTrace.begin("decode_payload")
defer { PerfTrace.end("decode_payload", id: traceID) }
let models = try decoder.decode([Item].self, from: fixtureData)

トークン、ファイルの内容、ユーザー識別子を signpost のテキストに書き込まないでください。性能計測の証拠はビルド成果物として保存されるため、フィールドはカーディナリティを低く保ち、安全にアーカイブできる内容にします。

xctraceの実行条件を固定する

まず GUI でテンプレート名と計測区間が実際に表示されることを確認し、その後でコマンドラインに切り替えます。テスト対象のアプリでは、固定したデータフィクスチャ、固定したシミュレータモデル、同一のビルド構成を使用します。記録前に古いプロセスを終了し、前回の実行状態が残らないようにしてください。

set -euo pipefail

OUT="$PWD/perf-artifacts"
mkdir -p "$OUT"

xcrun xctrace record \
  --template "Points of Interest" \
  --time-limit 45s \
  --output "$OUT/critical-path.trace" \
  --launch -- "$APP_PATH" \
  -PerfScenario first-batch \
  -PerfFixture "$PWD/Fixtures/items.json"

xcrun xctrace export \
  --input "$OUT/critical-path.trace" \
  --toc > "$OUT/toc.xml"

xctrace export の XPath やテーブル構造はツールのバージョンによって変わる可能性があります。そのため、解析スクリプトでは対象の schema が存在することを確認してから区間を読み取ります。列位置に依存する awk を長期的なインターフェースとして扱わないでください。エクスポートした XML の解析を優先し、バージョン更新時の契約テストに使える最小構成の trace を1つ保存しておきます。

LemonVM のリモート環境で実行する場合は、最初に xcodebuild -version、システムバージョン、ビルド構成、テストフィクスチャのハッシュを記録します。ノードまたはツールチェーンが変わった後は、新旧環境の数値を無理に比較せず、ベースラインを作り直してください。

複数回のサンプルからしきい値を設定する

1回だけの実行は、初回ロード、バックグラウンドのインデックス作成、温度状態の影響を受けやすくなります。最初に1回ウォームアップを行い、その後で少なくとも5回の有効な記録を取得することを推奨します。ゲートでは中央値を比較し、最も遅いサンプルには余裕を持たせた上限を設定します。

ベースラインの中央値が 420 ms なら、警告しきい値をベースラインの 1.15 倍、つまり 483 ms に設定できます。ただし、この倍率をすべての区間にそのまま適用してはいけません。数十ミリ秒の小さな関数と、数秒かかるデータ準備処理とでは、変動の特性が異なります。

判定スクリプトでは、少なくとも次の3種類の結果を区別します。

  1. trace を生成できない:インフラストラクチャの失敗として扱い、性能回帰とは判定しない。
  2. 対象区間が存在しない:シナリオまたは計測点の失敗として、テストを修正する。
  3. 有効なサンプルがしきい値を超える:性能ゲートの失敗として、証拠をアーカイブする。

ベースラインファイルはコードと一緒にレビューし、区間名、サンプル数、中央値、許容上限、ツールチェーンのバージョン、フィクスチャのハッシュを含めます。しきい値を引き上げる変更では、必ずコミットの説明に理由を記載し、しきい値が上がり続けるだけの状態を防ぎます。

よくある偽の回帰を除外する

初回だけ遅く、その後は安定している場合、一般にはキャッシュや動的リンクの準備が原因です。コールドスタートとウォームパスを別のグループとして扱ってください。すべての試行が遅い場合に、初めてコードの変更を確認します。1回だけ異常な場合は、その時点でインデックス作成、バックアップ、追加のシミュレータ、並行ビルドが実行されていなかったかを先に確認します。

さらに、次の項目も確認してください。

テストがグラフィカルセッションに依存する場合は、タスク開始前に表示状態の事前確認を追加します。純粋な解析処理やストレージ層だけを測る場合は、可能な限り独立したテストターゲットを使用し、UI の状態によるノイズを減らしてください。

失敗結果をすぐに対応できる形にする

ゲートの出力を「性能テスト失敗」だけで終わらせてはいけません。少なくとも、区間名、現在の中央値、ベースライン、変化率、有効なサンプル数、trace のパスを表示します。例:

FAIL render_first_batch
median_ms=512
baseline_ms=420
change=21.9%
samples=7
trace=perf-artifacts/run-06.trace

レビュー担当者は結果を見た後、対応する trace を直接ダウンロードして具体的な区間を特定し、業務コードの回帰なのか、計測環境の異常なのかを判断できる必要があります。安定した性能ゲートの目的は、毎回まったく同じ数値を得ることではありません。同じ入力、同じツールチェーン、同じ実行条件における変化を説明可能かつ再検証可能にすることです。最初は価値の高い経路を1つだけ選び、その変動を継続的に観察してから区間を段階的に増やすほうが、信頼できない指標を一度に数十個追加するよりも効果的です。

よくある質問

性能ゲートでは平均値と中央値のどちらを使うべきですか?

複数回の有効サンプルから中央値を使い、高いパーセンタイルまたは許容上限も併用します。平均値は一度のシステム揺らぎに影響されやすいためです。

xctraceの記録失敗を性能低下として扱うべきですか?

扱うべきではありません。記録失敗、区間不足、性能超過に別々の終了コードを割り当て、有効な計測結果が得られた場合だけ性能を判定します。

すべてのos_signpost区間をCIで検査できますか?

いいえ。開始点と終了点が安定し、固定入力で再現できる重要経路に限定します。人の操作や外部サービスの状態に強く依存する区間は診断用途に残します。

専用物理ノード

用途に合わせてLemonVMクラウドMacを選ぶ

Lemon M4とLemon M4 Proは東京、シンガポール、ソウル、香港、米国西部に対応し、日・週・月・四半期単位で利用できます。

クラウドMacを今すぐレンタル