WWDC Quick Look 💓 By SwiftGGTeam
Detect and diagnose memory issues

Detect and diagnose memory issues

元の動画を見る

ハイライト

Xcode 13 では、ktrace および memgraph 診断コレクションがパフォーマンス XCTest に追加されます。開発者は、メモリ回帰が発生した場合のテストの前後にメモリ スナップショットを取得し、リーク、vmmap、ヒープ、malloc_history を使用してリーク、ヒープの増加、断片化を特定できます。

主要内容

ユーザーが「レシピを保存」をクリックすると、アプリがデータをダウンロードし、インターフェースに「保存されました」と表示されます。機能は完成しているように見えます。

数週間後、古いデバイスのユーザーから、アプリがバックグラウンドから復帰し、頻繁に再起動されると報告され始めました。原因はメモリ不足である可能性があります。 iOS のメモリは限られているため、システムはメモリを再利用するためにバックグラウンド アプリを終了します。アプリのメモリ使用量が大きいほど、メモリに残る可能性は低くなります。

これまで開発者が使用していた一般的な方法は、問題が発生した後にメモリ マップを手動でキャプチャすることでした。このプロセスは遅すぎます。ユーザーが問題に遭遇した場合、開発者が同じ操作パスを再現することは難しく、どの提出物が増加を引き起こしたかを知ることも困難です。

このセッションで Apple が提示した解決策は、メモリ監視を XCTest に組み込むことです。新しい機能を組み込む前に、パフォーマンス テストを使用してメモリ指標を記録し、ベースラインを設定します。後続の実行の平均値がベースラインを超えると、テストは直接失敗します。

Xcode 13 の変更は診断フェーズにあります。パフォーマンステストに不合格になった後、xcodebuild診断ファイルを収集できます。非メモリ メトリックは ktrace によって収集され、メモリ メトリックは memgraph によって収集されます。 memgraph は、プロセスのアドレス空間、仮想メモリ領域、malloc ブロック、およびそれらの間のポインタ関係を記録します。開発者は、失敗したテストの前後のスナップショットを取得したら、成長がどこから来たのかを分析し続けることができます。

「戻るかどうか」から「戻る理由」へ

パフォーマンス XCTest は、「この実行はベースラインよりも悪いですか?」という最初の質問に答えることから始まります。

memgraph は、2 番目の質問「なぜメモリが増加するのか?」に引き続き答えます。講演では、一般的な問題をリーク、ヒープ割り当ての回帰、断片化の 3 つのカテゴリに分類しました。対応するツールは、leaksvmmapheapmalloc_history

メモリ使用量については、「ダーティおよび圧縮」を参照してください。

アプリのメモリ プロファイルは、ダーティ、圧縮、クリーンの 3 つのカテゴリに分類できます。ダーティ メモリとは、ヒープ割り当て、デコードされた画像バッファ、フレームワークなど、アプリによって書き込まれるメモリです。圧縮メモリは、最近アクセスされておらず、メモリ圧縮プログラムによって圧縮されたダーティ ページです。クリーン メモリとは、ディスク上のメモリ マップト ファイルなど、書き込まれずにページアウトできるデータです。

このセッションで説明するフットプリントは、ダーティ メモリと圧縮されたメモリを指します。ここでは、クリーンなメモリはフットプリントにカウントされません。

詳細

1. パフォーマンス XCTest によるメモリの測定 (04:52)

アップルが使用しているmeasure(metrics:options:block:)API では、「レシピの保存」UI パスをパフォーマンス テストに組み込みます。テストの目標は、XCUIApplicationメモリの使用量。

// Monitor memory performance with XCTests

func testSaveMeal() {
    let app = XCUIApplication()

    let options = XCTMeasureOptions()
    options.invocationOptions = [.manuallyStart]

    measure(metrics: [XCTMemoryMetric(application: app)],
            options: options) {

        app.launch()

        startMeasuring()

        app.cells.firstMatch.buttons["Save meal"].firstMatch.tap()

             let savedButton = app.cells.firstMatch.buttons["Saved"].firstMatch
        XCTAssertTrue(savedButton.waitForExistence(timeout: 30))
    }
}

キーポイント:

  • let app = XCUIApplication()UI テストでターゲット アプリ オブジェクトを作成します。
  • let options = XCTMeasureOptions()パフォーマンス測定構成を作成します。
  • options.invocationOptions = [.manuallyStart]テストで測定の開始点を手動で制御します。
  • measure(metrics: [XCTMemoryMetric(application: app)], options: options)ターゲット アプリケーションについて測定するメモリ メトリックを指定します。
  • app.launch()アプリケーションは開始されますが、測定はまだ考慮されていません。
  • startMeasuring()起動時の保存操作の結果への干渉を避けるために、メモリ メトリックのログを開始します。
  • app.cells.firstMatch.buttons["Save meal"].firstMatch.tap()ユーザーアクションを実行します。「保存」ボタンをクリックします。
  • let savedButton = app.cells.firstMatch.buttons["Saved"].firstMatch保存後の UI 状態を確認します。
  • XCTAssertTrue(savedButton.waitForExistence(timeout: 30))ダウンロードと保存が完了するまで最大 30 秒ほど待ちます。

Xcode は各反復の測定値を表示し、平均を計算します。開発者は、特定の平均値をベースラインとして設定できます。その後の平均はベースラインを超え、テストは回帰で失敗します。

2. ktrace と memgraph を収集する (07:49)

Xcode 13 では、パフォーマンス テスト診断コレクションが追加されています。コマンドラインエントリは次のとおりですxcodebuildenablePerformanceTestsDiagnostics flag。

xcodebuild test -enablePerformanceTestsDiagnostics YES

キーポイント:

  • xcodebuild testCI に組み込むのに適したコマンド ラインからテストを実行します。
  • -enablePerformanceTestsDiagnostics YESパフォーマンス テスト診断収集を有効にします。
  • 非メモリインジケーターは ktrace ファイルを収集します。このファイルは、インスツルメントを使用して分析用に開くことができます。
  • メモリ メトリックは memgraph ファイルに収集され、Xcode ビジュアル デバッガーおよびコマンド ライン ツールで分析できます。

テストが完了すると、コンソール出力には、テストが失敗したかどうか、回帰が原因で失敗したかどうか、現在の平均がベースラインよりどの程度悪くなっているかが示されます。xcresultバンドルへのパス。開けるxcresultその後、添付されたメモリグラフがテスト ログの下部に表示されます。

XCTest は追加の反復を追加して、malloc スタックのログを有効にします。メモリ テストでは 2 つのメモリグラフが取得されます。preは測定開始時のスナップショットです。post測定終了時のスナップショットです。これらは、反復内のメモリ増加を分析するために使用されます。

3. 最初に漏れがないか確認します (12:17)

ステファン氏は、失敗したテストのメムグラフを取得したら、まず漏れがないか確認することを提案しました。リークは、プロセスがオブジェクトを割り当てた後、すべての参照を失い、メモリが解放されない場合に発生します。 Swift での一般的な理由は、サイクルの保持 (強参照サイクル) です。

leaks post.memgraph

キーポイント:

  • leaksリークをチェックするためのコマンドラインツールです。
  • post.memgraphは、測定終了時のメモリのスナップショットです。
  • 出力には、リーク数とリークしたバイト数が表示されます。
  • 出力内のオブジェクト グラフは、リークされたオブジェクト間の参照関係を示唆する可能性があります。
  • 現れるROOT CYCLE問題は保持サイクルです。
  • XCTest は malloc スタックのログを自動的に有効にするため、出力にはリークしたオブジェクトの割り当てコール スタックも含まれます。

スピーチの中のミールプランナーの例では、leaks合計 240 バイトの 4 つのリークを示しています。オブジェクト グラフには以下が含まれますROOT CYCLEのシンボルは、食事プランとメニュー項目のオブジェクトに関連していることを示します。呼び出しスタックは割り当て場所を指し続けます。populateMealData

この修正は Swift ARC (自動参照カウント) によるもので、2 つのオブジェクトが相互に強い参照を保持するのを避けます。ビジネスで逆の関係を維持する必要がある場合は、参照の 1 つを弱い参照に変更します。

weak var mealPlan: MealPlan?

キーポイント:

  • weakこのプロパティが参照先オブジェクトの参照カウントを増加させないことを示します。
  • MealPlan?弱参照オブジェクトは自動的にオブジェクトになるため、オプションの型である必要があります。nil
  • 後方参照を弱参照に変更すると、2 つのオブジェクトは強参照による保持サイクルを形成しなくなります。
  • ARC は、外部参照が消えた後にオブジェクトを解放できます。

4. vmmap とヒープを使用してヒープの増加を確認します (15:27)

リークがない場合は、次にヒープを調べます。ヒープ割り当ての回帰とは、プロセスが以前よりも多くのオブジェクトをヒープ上に割り当て、フットプリントが増大することを意味します。

vmmap -summary pre.memgraph
vmmap -summary post.memgraph

キーポイント:

  • vmmap -summaryプロセス メモリの使用状況の概要を示します。
  • 測定前と測定後を別々に表示して、測定前後のフットプリントの違いを確認します。
  • に従ってくださいMALLOC_これらの領域にはヒープ オブジェクトが含まれているためです。
  • 存在するvmmap出力中、swappediOS の圧縮メモリに対応します。
  • フットプリントを分析するときに見るdirty sizeそしてswapped size

スピーチの例では、プレ フットプリントは約 112 MB、ポスト フットプリントは約 125 MB で、約 13 MB 増加しています。MALLOC_LARGEこの領域には、回帰サイズと一致する約 13 MB のダーティ メモリが保持されます。

次に、使用しますheap -diffFrom投稿にどのような新しいヒープ オブジェクトが追加されるかを確認してください。

heap post.memgraph -diffFrom pre.memgraph

キーポイント:

  • heapmemgraph 内のヒープ オブジェクトを検査するために使用されます。
  • post.memgraph分析対象のターゲット スナップショットです。
  • -diffFrom pre.memgraphpost には存在するが pre には存在しないオブジェクトのみが表示されます。
  • 出力には、オブジェクト クラスごとのオブジェクトの数と合計バイト数が表示されます。
  • たくさんnon-objectSwift では、通常、生の malloced バイトを意味します。

スピーチの例では、heap -diffFrom約 13 MB の新しいオブジェクトが表示されます。その主なタイプは次のとおりです。non-object。これは、未処理の割り当てられたバイトを引き続きチェックする必要があることを意味します。

5. アドレスから割り当てコールスタックまで追跡する (17:23)

からの成長を確認non-object最後に、最初に特定のアドレスを取得します。

heap post.memgraph -addresses non-object -minSize 500000

キーポイント:

  • -addresses一致したオブジェクトのアドレスを出力します。
  • non-object未処理の割り当てられたバイトのみをフィルタリングします。
  • -minSize 500000少なくとも 500 KB のオブジェクトのみを確認することでノイズを軽減します。
  • 約 13 MB のオブジェクトが見つかると、それが主な容疑者と見なされます。

アドレスを使用すると、誰が参照したか、どこに割り当てられたかがわかります。

leaks --traceTree 0xADDRESS post.memgraph
leaks --referenceTree --groupByType post.memgraph
malloc_history post.memgraph -fullStacks 0xADDRESS

キーポイント:

  • leaks --traceTree 0xADDRESS post.memgraphこのアドレスを参照するオブジェクトツリーを表示します。
  • leaks --referenceTree --groupByType post.memgraph参照ツリーをルート オブジェクトから下位に集約し、タイプごとにグループ化します。
  • malloc_history post.memgraph -fullStacks 0xADDRESSアドレス オブジェクトの完全な割り当てコール スタックを表示します。
  • malloc_historymalloc スタックのロギングに依存します。 XCTest によって収集されたメモリグラフは自動的に有効になります。

演説中、leaks --traceTreeそしてleaks --referenceTreeすべてが指すmeal data物体。malloc_history -fullStacksポイント配分先saveMeal関数。問題は、セルが大きなバッファを保存し、ディスクに書き込まれた後もクラス メンバーがバッファを保持していることです。修復方法は書き込み完了後にリファレンスをクリアすることです。

mealData = nil

キーポイント:

  • mealDataクラスのメンバーであるため、参照はセル インスタンスが存在する限り存在し続けます。
  • ディスクに書き込んだ後も大きなバッファを保持し続けると、保存された各セルがメモリを保持できるようになります。
  • mealData = nil最後の参照を削除した後、Swift のData基礎となるバッファが解放されます。
  • この修正により、大きなオブジェクトの寿命が短縮され、ピーク時のフットプリントが削減されます。

6. 断片化を確認する (24:51)

断片化とは、完全に使用されていないダーティ ページを指します。 iOS ページは固定サイズの分割不可能なブロックです。プロセスがページの一部を書き込んだ後は、ページ全体がダーティであるとみなされます。オブジェクトが解放された後に残った穴は、その後の割り当てに対応できない場合には無駄になります。

vmmap -summary post.memgraph

キーポイント:

  • 同じvmmap -summary断片を閲覧することができます。
  • 出力の下部にある malloc ゾーンまでスクロールします。
  • 見て% FRAG各 malloc ゾーンでの断片化による無駄の割合を表す列。
  • 通常の状況でも注意してくださいDefaultMallocZone
  • malloc スタック ロギングが有効な場合、memgraph ヒープ割り当てが行われます。MallocStackLoggingLiteZone
  • Apple の経験的な目標は、断片化率を約 25% 以下にすることです。

断片化を減らす原則は、同様のライフサイクルを持つオブジェクトをメモリ内でより近くに配置することです。これにより、リリース時により大きな連続空き領域を形成することができます。この講演では、プールの自動解放についても触れられています。プールが範囲外になった後、プール内に作成されたオブジェクトは解放され、これらの一時オブジェクトは同様のライフサイクルを持つようになります。

autoreleasepool {
    // Allocate temporary objects that should be released together.
}

キーポイント:

  • autoreleasepoolローカル リリース スコープを作成します。
  • スコープ内の一時オブジェクトは、プールが終了すると解放されます。
  • 同様のライフサイクルを持つ一時オブジェクトのバッチをまとめるのに適しています。
  • 長時間実行されるプロセスまたは拡張機能の場合、断片化を定期的にチェックする必要があります。

重要ポイント

1. 高リスクの UI プロセスにメモリ回帰テストを追加する

  • やるべきこと: 「保存」、「インポート」、「エクスポート」、「オフライン パッケージのダウンロード」などのプロセスの UI パフォーマンス テストを作成します。
  • 実行する価値がある理由: これらのプロセスにより、大きなオブジェクトを簡単に作成できます。XCTMemoryMetricメインブランチに退行する前に失敗する可能性があります。
  • 開始方法: を使用しますXCUIApplicationで対象のページを開きますXCTMeasureOptions設定.manuallyStart、キーがクリックされる前に呼び出されますstartMeasuring()

2. memgraph コレクションを CI に接続します

  • 内容: パフォーマンス テストの実行時に CI が有効になります-enablePerformanceTestsDiagnostics YES、失敗したテストを入れますxcresultビルドプロダクトとして保存します。
  • 実行する価値がある理由: 回帰が発生した場合、開発者は再度再現することなく、事前/事後のメモリグラフを直接ダウンロードできます。
  • 開始方法: 既存のxcodebuild testコマンドの後にフラグを追加し、CI にアーカイブします。xcresultパス。

3. 大きなオブジェクトのリリース チェックリストを作成する

  • 何をすべきか: 画像デコードバッファを確認し、データをダウンロードし、ファイルをインポートし、一時Data使用後に解放するかどうか。
  • 実行する価値がある理由: 講演中の 13 MB の回帰は、ディスクへの書き込み後もクラス メンバーによって保持されているバッファーに起因しています。
  • 開始方法: を使用しますheap -diffFrom成長タイプを見つけるには、次を使用します。malloc_history -fullStacks割り当て関数を見つけて、操作の終了時に最後の参照をクリアします。

4. 保持サイクルの固定トラブルシューティング パスを確立する

  • 何をすべきか:leaks post.memgraph記憶退行の最初のステップとして。
  • 実行する価値がある理由:ROOT CYCLE循環参照を直接指定でき、malloc スタックのロギングによりオブジェクトの割り当て位置を明らかにできます。
  • 開始方法: 最初にポストメモリグラフを取得して実行しますleaks、出力のアプリ シンボルから Xcode と入力し、双方向参照を確認し、逆の関係を に変更します。weak

5. 長時間実行される拡張機能の断片化率を確認する

  • 対処法: 定期的に使用してくださいvmmap -summary拡張機能の malloc ゾーンの断片化率を確認します。
  • 実行する価値がある理由: 長時間実行されるプロセスでは、大量の割り当てと割り当て解除が行われるため、アドレス空間が断片化しやすくなります。
  • 開始方法: メモリグラフを取得して表示します% FRAG、 焦点を当てるDefaultMallocZoneまたはMSLが有効な場合MallocStackLoggingLiteZone、約25%以下を目指します。

関連セッション

コメント

GitHub Issues · utterances