ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Appのハングアップの理解と解消
Appで発生するハングアップや遅延を突き止める方法を紹介します。本セッションでは、ハングアップとその原因を見つけ出すためのツールや手法を紹介し、ハングアップにつながるアンチパターンについて確認し、GCDのようなハングアップを解消するためのベストプラクティスを検証して、Appのパフォーマンスを向上させるために非同期コードを検討すべき場合の指針を示します。
リソース
関連ビデオ
WWDC23
WWDC22
WWDC21
WWDC20
WWDC19
WWDC17
WWDC16
-
ダウンロード
こんにちは 私はアヌバウです OSパフォーマンスチーム のエンジニアです 今日はアプリケーションから ハングを理解し 排除する方法を ご紹介します この話を4つのセクションに 分け「ハングとは何か?」を 理解することから 始めます そして ハングの 一般的な原因と 発生時の注意点について お話します その後 ハングを監視・診断 するツールを説明します 最後に ハングを解消する ための一般的な戦略と Appに最適な戦略を 選択する方法を学びます それでは早速 ご紹介しましょう 私の新しいレシピApp Dessertedを見てみましょう これは 私のお気に入りの 飲み物やデザートの 作り方を紹介する アプリケーションです マンゴタンゴのスムージー は美味しそうですね どんな風に作られているのか タップしてみます うーん… 何も起こって いないようですね… あー 予想以上に 時間がかかりましたね Appが固まってしまい 数秒間タッチを 受け付けない状態に なってしまいました
これは「ラグ」 「遅い」や 「固まった」と言われます これらは 私や他の誰もが 自分のAppに対して 使いたい言葉では ありません Appleではこの無反応の 期間を「ハング」と呼びます
Dessertedで起きていた ハングを理解するためには Appのメインランループが 何かの理解がまず必要です
メインランループとは Appのメインスレッドが 主にユーザーとのやりとり などのイベントに応じて イベントハンドラを 実行するためのループです ユーザーがAppを使うと ランループはそのイベントを 受け取り 処理し 必要に応じて UIを更新します すべてランループの1ターン メインスレッド上で起ります このプロセスは ユーザーの 入力ごとに繰り返されます ランループを1回転させると こうなります
イベントの処理に 時間がかかる場合は ユーザーの入力とUIの更新 の間に遅延が生じます さらに悪いことに イベント はバッファリングされるため ハング中のメインスレッド では処理できません ハング中にAppを操作 しても そのイベントは 現在のハングがまず終了 するまで処理されません 複合的なハングが 重なっていきます
一般的に 1秒以上の遅延は 通常ハングだとわかりますが もっと短い遅延でもハング だと気づくこともあります 例えば スクロール時に半秒 遅れると違和感がありますが ビューに入る場合であれば 同じ遅れがずっと気にならなくなります
ハングをなくすと動作が軽快 になり反応が早くなります
ハングがわかったので一般的 な原因を見てみましょう
ハングはメインスレッドでの 作業が多すぎると発生します 作業を正確に判断するには イベントを処理している間に メインスレッドがしている ことを見なければなりません この時間は2つのケースに 分けられます メインスレッド自身が 作業に追われている場合 1つの長いタスクもあれば 複数の短いタスクもあります また別のスレッドやシステム にブロックされている場合 メインスレッドがビジーに なる主な原因を見ましょう まず考えられる作業はUIの 更新に必要以上の作業を行い メインスレッドをより長く ビジーにすることです
Dessertedではレシピビュー には多数の材料画像のうち 4つの画像タイルしか 表示されません メインスレッドがすべての 画像を一度に読み込む場合 各画像を読み込んで準備し 合成するのに時間が必要です ここでの作業のほとんどは ユーザーには影響しません
ビューには4枚の画像しか 表示されませんが その4枚だけはすぐに 生成する必要があります
メインスレッドでの無関係な 作業も原因となります メインスレッドはディスパッチ キューからのブロックを 処理しますが ディスパッチ同期により 他のキューからのブロックも 処理できます キューのディスパッチが他の キューに同期するたびに 他のキューで保留されている すべてのブロックは 新たに キューに入れられたブロック よりも先に実行が必要です 優先度の低いシリアル ディスパッチキュー おそらく メンテナンスキューを備えた Appを考えてみましょう メインスレッドディスパッチが メンテナンスキューに ブロックを同期させた場合 キューに入れられたブロック が実行される前に キューにある保留中の ブロックがすべて実行されるのを 待たなければなりません メインスレッドのために 使われた時間はわずかでした
同様にブロックが別のキュー からメインキューに ディスパッチされた場合 ブロックはメインスレッドで 実行する必要があります
これはブロックが非同期の ディスパッチでエンキュー された場合でも同様です
ハングのもう1つの原因は 最適ではないAPIの使用です タスクを達成する方法は たくさんあります 必ずAPIのドキュメントを 読んで目的に応じて 最適なものを使ってください
Dessertedではレシピビュー の画像に角丸を追加しますが これを行うと ビューに入る ときに待ち時間が発生します
角丸を追加するために Dessertedはビットマップベース のグラフィックコンテキスト を使って画像を変換し そこにUIBezierパスを適用し 再び画像に変換しています この一連の操作はCPUに負荷 がかかりメモリを多く使用し 長い時間がかかる 可能性があります これは非適切なハードウェア を使っていることが原因です CPUを使うのではなくGPU を活用すべきだと思います
CoreAnimationメソッドを レイヤーに使用することで 角丸を簡単にすばやく 追加できます これは作業に適していない APIを使用した一例です
メインスレッドがビジーに なる理由を見てきたので なぜブロックされるのかを 調べてみましょう
同期APIは呼び出しから 戻る間実行をブロックします APIが多くの作業を行う場合 や長時間ブロックする可能性 がある場合にメインスレッド で使用すべきではありません 遅延だけでなく 失敗の原因にもなります
例えば Appのメイン スレッドがネットワークに 同期リクエストを 行う場合などです 5Gを利用している人にとって は遅延はないかもしれません ネットワーク速度が遅い場合 時間がかかることがあります 電波状況が非常に悪い場合は ずっと続くかもしれません どれだけの時間がかかるかは 保証されていないので このような同期操作はメイン スレッドでは避けるべきです
メインスレッドをブロック する別の原因は システムリソースで 多くの 場合に制約があるためです ファイルI/Oは最も一般的に 使用され競合している システムリソースの1つです 遅延はハードウェアに依存し 他の読み書きが同時に起こるなど Appではコントロール できないことがあります Appはメインスレッドでの I/Oを避け ハングを 防ぐためにできることを する必要があります
並行性をサポートしない データストアは特に問題です 書き込みがすでに行われて いる間にメインスレッドが 読み取りを行おうとすると その読み取りはすべての 書き込みが完了するまで排除 されますが きりがないかも しれません
またハングの原因の1つに 同期があります 定義上同期プリミティブは 実行をブロックすることが できるのでメインスレッド からの同期を制限し 慎重に行うことが 重要です
同期先のスレッドが暗黙的 または明示的なロックを 解放するのに 時間がかかることがあります 一般的なプリミティブを 紹介します @synchronized directive dispatch sync os unfair lock posix locks などです 特にセマフォは注意が必要で 優先順位が伝わらないため プリエンプションで ハング が長くなる可能性があります よくあるアンチパターンは セマフォを待つことで 非同期APIを同期的に 動作させようとするものです これはメインスレッドでは 常に回避する必要があります
メインスレッドをブロックする もう1つの方法は 作業 IPC またはシステムリソースを 使用して頻繁に変更されない ものの値を 取得することです
Dessertedにはソーシャル 機能のアイコンがありますが 連絡先を友達として追加した 場合にのみ表示されます このビューにタップするたび にすべての連絡先を照会する ことは 確認のための 1つの方法ですが 不必要な作業管理を増やし 遅延を発生させます メインスレッドがフレーム ワークをブロックしてるので 内部で高価な処理を 行っています さらに 取得する値は頻繁に 変化するものではないので 頻繁に照会する必要もなく システムに負荷がかかります
CPU メモリ ストレージなどの システムリソースの状態が ハングの発生には 大きく影響します フィールドではハードウェア やデバイスの状態が異なり 実世界のシナリオは机上での テスト結果と随分異なります 厳しいテストを行いサポート されている最も古い ハードを基準として使用する ことでこれらのケースに対し できる限りの防御策を 講じることが重要です ハイレベルのハングの原因は メインスレッドでの作業 またそのために行われる 作業が多すぎることです パフォーマンスを確保する にはAppのメインスレッド が UIの更新に必要なことに 集中することが重要です
さて ハングの一般的な 原因がわかったところで 開発時と作成時の両方で Appのハングを監視して 対処するために使える 便利なツールをご紹介します
ハングを対処するために その間にAppが 何をしているかを 知りたいと思うでしょう タイムプロファイラは アプリケーションの コールスタックを 時系列で表示し 何が実行されているかを 正確に示してくれます システムトレースツールは システムコール VMフォールト I/O またプロセス間 およびプロセス内の インタラクションに関するデータで コンテキストを追加します 詳しくは2016年に行われた 「System Trace in Depth」の セッションをご覧ください 次にタイムプロファイラと システムトレースツールを使い Dessertedのハングの 原因を探ります
ハングの追跡をした後 Instrumentsで開いた状態 はこんな感じです システムトレース出力では 赤い線がシステムコール 紫のグラフが仮想メモリの 障害を示しています 水平の青いバーは メイン スレッドがビジーで処理中 であることを示しています 次のステップは この作業が 何であるかの確認です タイムプロファイラはまさに それを可能にしてくれます 4.7秒のハングの間 メインスレッドの コールスタックを集計して コールツリーを表示します ツリーのハイライト部分から このハングのうち4.6秒は レシピビューの loadAllMessages メソッドによるもので あることがわかります このパターンには 見覚えがあります Dessertedは 必要以上の 画像を読み込んでいる 可能性があります
Appが出荷されたら MetricKitを使って フィールドでヒットしたハング のコールツリーを収集します これによりお客様が最も多く 直面している問題に基づいて 修正の優先順位を 決めることができます ハングのためのMetricKitの使い方 については2020年の "MetricKitの新機能" のセッションをご覧ください Dessertedを出荷しましたが MetricKitからいくつかの ハングレポートがあります 先に対処したハングに 似ているかどうか その1つを見てみましょう
MetricKitは ハング中に 取得したコールスタックを 集約することで コールツリーを返します このツリー形式は タイム プロファイラと同じです
強調表示されている部分は 先ほどInstrumentsで 調べたハングとは異なる ことを示しています これは 私が追加した新しい ソーシャル機能によるもので 連絡先を照会するディスパッチ キューをブロックするものです MetricKitがないとこの問題 を発見できなかっただろうし フィールド内に今もなお 残っていたでしょう ハングを修正するときは Appのパフォーマンスを ベースライン化し 定量化 することが重要です Xcode OrganizerはAppの バージョンごとのハング率を 示す表など パフォーマンス 指標を表示しています これはregressionを対処 するときに特に役立ちます Xcode Organizerの詳細は 次の2講演をご覧ください
それではAppのハングを 修正するために使える 一般的な戦略を説明します
これらの戦略はそれぞれ ハングの複数の原因に 対処することができます 自分のAppに最適な 修正方法を知るためには それぞれの別の影響や代償を 考慮する必要があります
ハングをなくし 防御するためには メインスレッドで実行される 作業量を減らします
これには2つの 方法があります 1つは メインスレッドで すでに実行されている作業を 最適化して 実行時間を 短縮することです 2つ目は ノンブロッキング 方式でメインスレッドから 作業を移動させレスポンス を維持することです まずはメインスレッドの実行 を減らす方法として キャッシングから 見ていきましょう キャッシュは頻繁に使われる アセットや以前に求めた値に 素早くアクセスするための 優れた方法です メモリ上に保存されますが 複数のAppを起動する際に 必要であれば ディスクに 存続することもできます Dessertedの食材画像タイル のように 後で必要に なるかもしれない フォーマットされたアセット これらは必要になるたびに 作成して 増えていくために キャッシュに適しています
NSCacheにキャッシュすることで アセットを生む付帯コストを 迅速なメモリの読み込みに 置き換えることができます これによりInstrumentsで 見たハングが解消されます
古いデータを持つことと 常にキャッシュを更新する ことをうまく両立 させるために 正確なキャッシュ無効化機構 を持つことが重要です この作業はメインスレッドが イベントに対応できるように 第2のdispatch queueで 非同期的に行われるべきです
通知オブザーバは メイン スレッドでの作業を 減らす別の方法です 高価なオンデマンド計算を 行うことなく これにより 値や状態の変化に 対応することができます 自分のクラスや どのクラス でも通知を投稿できます 特定のクラスからの通知を 確認するには そのAPI ドキュメントを確認します 注目すべきあらゆる システム通知を見つけるには Appleの開発者向け ドキュメントページの nsnotification.name をご覧ください その有力な候補がDesserted のソーシャル機能です
abDatabaseChangedExternally 通知のオブザーバを 登録することで メインスレッドがコンタクト 申請を待つ必要が なくなりました 通知が届くと オブザーバが起動されます この場合キャッシュされた 値を更新することになります
メインスレッドの応答性を 維持するには これらの更新 を非同期にする 必要があります これはハンドラを別のキュー にdispatch_asyncingする ことで実現されます
さて 前と同じ機能を 提供しますが MetricKitのログで見た ハングはありません ハングアップをなくすために 作業をメインスレッドから 外すという方法もあります まずこの作業がどうあるべき かを決める必要があります 一般的にUIに重大な情報を 提供する重要なタスクは メインスレッドに残す 必要があります さらに すべてのビューと ビューコントローラは メインスレッドで作成 変更 また破棄する必要があります しかしUI要素の更新に必要 な計算は メインスレッドで 実際の更新を行うための 完了ハンドラを使って 別のスレッドにオフロード することができます これは 計算に時間がかかる とわかる場合に有効です 他にメンテナンスや 重要度 の低い急を要さないタスクは 別のスレッドで非同期的に 実行されるべきです
これらは予定の優先順位が 低くなりメインスレッドでの 作業よりも完了までに 時間がかかることがあります これは意図的なものでメイン スレッドは重要な作業のみを 行うべきであるという 考えを反映しています
メインスレッドから 非同期操作を実行する 最も簡単な方法は 同期APIの 非同期対応部分を 使用することです ネットワークを例に 挙げてみましょう
同期ネットワーキングAPIに 対応する非同期NSURLを 使用することで Appの応答性が向上します 非同期APIはasynchronously という言葉やメソッド名に 完了ハンドラが含まれている ことで示されることが多い
Grand Central Dispatchは 強力なマルチスレッド機能で 非同期APIの変異型がない 場合やメインスレッドから 移動させたいコードが独自の ものの場合に活用できます Grand Central Dispatchは 任意の作業ブロックを別の スレッドに 同期的または 非同期的に移動するための 単純なメカニズムを 提供します これによりGCDはハングの 最もよくある原因を 排除するのに 非常に効果的です
別のスレッドで非同期的に 作業ブロックを実行するには 別のディスパッチキューに ブロックを非同期に送ります
非同期ブロック内では メインキューに戻すことで 完了ハンドラを 追加することができます
Grand Central Dispatchでは 計算を事前にウォームアップ しておくこともできます プリフェッチキューなどの キューにタスクを 非同期化することで メインスレッドが 他の作業をするために 開放している間に タスクの実行が開始されます これらの結果がメイン スレッドで必要になると メインスレッドは同期を プリフェッチキューに ディスパッチして タスクが 完了するのを待ちます GCDにできることはまだ ほんの少ししかありません 詳細については 2017年の 「Modernizing Grand Central Dispatch」の講演を ご覧ください
先ほど話したソリューション とのトレードオフを 理解しましょう キャッシュはメモリを使用 するので、メモリが大きく 増えないようにサイズを認識 しておく必要があります 値が古くならないように 正確な無効化メカニズムが あることを確認することも 重要です 通知が増える 可能性があります 観察する際には その通知が 発生する頻度を 考慮することが重要です 複数の通知を処理または合体 する前に フィルターを 追加することで CPUチャーン を減らすことができます 非同期APIを使用する場合 問題の操作を非同期に する必要があるかどうかを 知ることが重要です 特に OSは非同期作業の 優先順位を下げるため UIの更新が重要かどうかを 最初に確認します Grand Central Dispatchを 使って非同期にタスクを実行 する場合 コード内のタスク の実行順序を 変更することになります Appが壊れないようにする ためには他の人にどのような タスクを発注するべきか を念頭に置くことが重要です シリアルキューで dispatch_syncを使用すると 必要に応じて操作を 同期させることができます ハングによるユーザーが経験 する深刻な影響に比べれば これらのトレードオフは 常に価値のあるものです
ハングをなくすために 注意すべき点は Appleのフレームワークと APIを使用することです これらはもう幅広いデバイス に対応しており 性能も高く より効率的で効果的になる ように常に更新されています コードで繰り返し 改善を行ないます このようにして 対象を絞った修正を行い 個々の変更の影響を 確認できます システムリソースを使用する 際は良き隣人でいてください 必要以上のリソースを使用 すると Appの パフォーマンスが 低下するだけでなく システムの他の機能低下 の原因にもなります 私たちは ハングがどれほど 厄介なものであるか また Appでハングを防ぐ ことがどれほど重要であるか を経験しました 今後はXcodeオーガナイザー を使用してAppの パフォーマンスベースライン を設定します 開発やコードレビュー の際には ハングの 原因となるアンチパターン に注意してください 最も代表的な7つについて 説明しました タイムプロファイラや システムトレースで 発生した問題を診断し MetricKitを使ってお客様が 最も頻繁に遭遇する問題に 優先順位をつけます キャッシュを使ったり通知を 観察したり 非同期の 代替手段を探したり Grand Central Dispatchを 活用したりして 見つけたハングを解消します 以下の手順を踏むことで Appの動作がさらに向上し 最高のユーザー体験を 提供することができます お付き合いいただき ありがとうございます [音楽]
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。