ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
iOSメモリについて理解を深める
Appのメモリ使用量に影響を与えているものを特定する上で、メモリグラフをどのように活用できるかを紹介します。1枚の画像にかかる実際のメモリコストについて学習し、Appのメモリ使用量を削減するヒントやコツについてもご確認ください。
リソース
関連ビデオ
WWDC23
WWDC21
WWDC18
-
ダウンロード
(音楽)
(拍手) Appleのソフトウェアエンジニアの カイルです 本日はiOSのメモリの話をします iOSにフォーカスした話ですが 他のプラットフォームへも 適用可能です
最初のトピックは なぜメモリを減らすのか?
ここで言うメモリとは メモリフットプリントです 後ほど詳しく説明します 次にメモリフットプリントを 分析するツールの話 画像がバックグラウンドにある時の 最適化の話もします そして最後はデモです さて なぜメモリを減らすのか?
ユーザの使い勝手を よくするためです Appの起動ばかりか 動作も速くなります 誰のAppでも メモリに長くとどまります 利点ばかりです 今 隣に座っている デベロッパを助けているのです
メモリフットプリントの 話をしましょう すべてのメモリは異なります その意味とは? ページを例にします 紙のページとは違い メモリのページです システムから提供されて ヒープ上で 複数のオブジェクトを 所持できます 数ページに渡る オブジェクトもあります
サイズは大抵16キロバイトです クリーンかダーティな ページに分かれます
メモリ使用量とは ページ数とページサイズの積です
クリーンとダーティの 例を挙げます 例えば 2万個の整数を 配置するとします そのシステムには 6ページ要するかもしれません
配置時のページはクリーンです ただデータバッファに 何かを入力した場合 例えば先頭部分に入力すると ページはダーティになります 同じように データバッファの最終ページに 入力してもダーティになります 挟まれた4ページは クリーンなままです
さらに興味深いのは メモリマップトファイルです これはディスク上で メモリにロードされたファイルです 読み込み専用ファイルは 常にクリーンなページになります カーネルがRAMへの 移動を管理します
分かりやすい例はJPEGです このJPEGのサイズが 50キロバイトの場合― メモリがマップされ ページにすると約4ページです 4ページ目は空きがあり まだ使用可能です 少しトリッキーですね 最初の3ページはパージ可能です
通常のAppでは― フットプリントの要素はダーティ 圧縮 クリーンに分かれます 詳しく説明しましょう
クリーンなメモリは ページアウト可能です これがメモリマップトファイルです 画像やBLOBデータ トレーニングモデルなど
フレームワークでもよいです フレームワークは DATA CONSTを持ち swizzlingなどを実行すると ダーティになります
ダーティはAppによる 書込みがあるメモリです ダーティメモリにはオブジェクトや 文字列や配列などの mallocで定義された あらゆるものがあります
またデコードされた イメージバッファや
フレームワークもそうです フレームワークには通常のデータと ダーティな部分がありますが ダーティとして認識されています フレームワークは 2度登場しました フレームワークをリンクするには 両方のメモリを使うのです フレームワークのリンクの 話をすると このフレームワークの整備には Singletonsと グローバルイニシャライザが ダーティメモリ削減に役立ちます Singletonsは生成された後 必ずメモリに起生し イニシャライザはリンク時 クラスのロード時に実行されます
次は圧縮されたメモリです iOSはディスクスワップの システムがありません
代わりに メモリ圧縮プログラムを使います iOS 7でリリースされました この圧縮プログラムは 未アクセスのページを― 圧縮してスペースを生み出します またアクセス時にはページを解凍し メモリを読み取れます 例を見ましょう キャッシュ用の ディクショナリがあるとします 使用メモリは3ページ分 アクセスがなく システムがスペースを要求すれば 1ページまで圧縮できます このように圧縮されて
2ページ分の空きができました アクセスすれば元に戻ります
次はメモリ不足警告について 警告の原因がAppとは限りません ローメモリのデバイスでの 電話受信は― 警告の原因になります 皆さんのAppが 原因ではありません この圧縮プログラムは メモリ開放を複雑にします 圧縮対象によっては よりメモリを使うからです そこでポリシーの変更を お薦めします 一定期間キャッシュを停止するとか バックグラウンド作業の 一部を抑制するなど
対応済みの方も いるかと思いますが―
警告を受けたら キャッシュから すべてのオブジェクトを削除します
圧縮されたディクショナリの 例に戻りましょう どうなるでしょう? ディクショナリにアクセスすると ページの使用数が増えました 制限されたメモリ環境では 問題ですね 私はオブジェクトを削除し 圧縮時と同じ1ページに戻すため 多くの作業をしています 一般的に メモリ不足警告には要注意です
さてキャッシュに関する 重要ポイントです キャッシュは CPUのリピート作業を避けます 度を越えると メモリを使い果たし システムには問題です メモリ圧縮プログラムと キャッシュがありますが キャッシュと再計算の バランスを考慮してください またディクショナリの代わりに NSCacheを使えば 安全にオブジェクトを保存できます NSCacheで割り当てられた メモリはパージ可能で メモリ制約下では有効です
Appのメモリ構造に戻りましょう Appのフットプリントと言えば ダーティか圧縮の部分です クリーンは別ものです どんなAppにも フットプリントの限度があります 上限はかなり高いですが デバイスによって 限度は変わります 1ギガバイトのデバイスでは 4ギガバイトのデバイスほど メモリは使えません
そこでExtensionを使います Extensionの フットプリントは低いです 使用の際は 心に留めておいてください
フットプリントが超過すると 例外を得ます それがEXC RESOURCE EXCEPTIONです それではこれから フットプリントの分析について ジェイムスが話します
(拍手) よろしく ジェイムス ありがとう
ありがとう カイル Appleのソフトウェアエンジニアの ジェイムスです フットプリントの分析のための ツールを紹介します
Xcodeのメモリ測定は ご存知ですね デバッグナビゲータに表示され フットプリントを確認できます Xcode 10ではシステムの グレードも表示されますが― Xcode 9との違いは 気にしないでください XcodeでAppの実行をし 多くのメモリを消費しています 調査に使うツールは? Instrumentsです これで様々な方法で フットプリントの調査ができます
AllocationsとLeaksは ご存知ですね Allocationsは ヒープの配置を分析し Leaksはメモリのリークを チェックします そして聞きなれないVM Trackerと Virtual Memory Traceです
カイルがiOSの メモリについて話しました― ダーティおよび圧縮メモリです VM Trackerはそれらを 分析できます ダーティと圧縮の 別々の記録を持ち レジデントサイズの情報を 提供します ダーティメモリのサイズ調査に 有効です
最後はVirtual Memory Traceです Appの仮想メモリシステムの パフォーマンスを表示します
Operationタブから見られます 仮想メモリの分析や ページのキャッシュヒット数や ページのゼロフィルも表示されます
デバイスのメモリが上限に近づくと 例外の警告を受け取ります Xcode 10でAppを起動すると Xcodeが例外をキャッチし Appを停止します メモリデバッガを起動し 調査を開始できます とても便利です
Xcode 8より導入されました オブジェクトの依存 サイクル リークを追跡できます Xcode 10には レイアウトのよい更新がありました 大きなmemgraphが 見やすくなりました Xcodeがmemgraphを利用し Appのメモリ情報を蓄積しています 実は コマンドラインツールでも memgraphを使えます
まずXcodeからmemgraphを エクスポートします とてもシンプルです エクスポートボタンを押して 保存するだけです そして そのmemgraphを コマンドラインに移します 私がXcode 10でAppを実行し 例外を受け取ったとして memgraphを使い調査します 何をするのか? ターミナルへ行きます
使うツールはvmmapです プロセスに割り当てられた 仮想メモリ領域を表示することで Appのメモリ消費の分析が 可能になります
まずはsummary flagを お薦めします 領域のメモリのサイズを 詳細に表示し
ダーティな領域も表示 圧縮されたメモリ量も示します ダーティやスワップは とても重要です ただスワップサイズは 圧縮される前のサイズだということ 圧縮サイズとは違います より詳細な情報を望む場合 memgraphに対しvmmapを実行し コンテンツを取得してください すると実行可能コードのような― 書き込み不可能な領域が判明します これでインスタンスなどの 書き込み可能な領域も判明します ヒープがある場所です
すべてのツールがコマンドライン ユーティリティでうまく機能します 先日VM Trackerで 私のAppを分析したところ ダーティメモリが増加していました そこでmemgraphを使い ダーティデータの原因となる― フレームワークや ライブラリを探しました
その時のmemgraphに 実行したvmmapです ページフラグを使いました バイト数の代わりに ページ数で表示されます
私は検索のため グレップに― “ダイナミックライブラリ”と 挿入しました
最後にawkスクリプトを挿入して ダーティなカラムを合算し ページ数を表示させます
便利なので 常にこれを使っています デベロッパにとって有効な デバッギング作業となるでしょう
macOSデベロッパはleaksを ご存じかもしれません ランタイムにどこにも根付かない オブジェクトを記録します leaksで見つかったオブジェクトは 開放不可なダーティメモリです
メモリデバッガのリークを見ると 互いに関連する 3つのオブジェクトがあります leaksツールで見てみましょう
最新のleaksは リークされたオブジェクト以外に
それらが属するサイクルも 表示されます mallocスタックログが プロセス上で可能なら ルートノードの バックトレースも提供されます
メモリはどこへ行くのでしょう? vmmapを調べると ヒープは本当に大きいです ヒープツールはプロセス上すべての オブジェクト配置情報を提供します 同じ種類のオブジェクトや 配置の追跡に役立ちます
Xcodeがメモリリソースの例外を キャッチした際のmemgraphです ヒープを調査します するとオブジェクト情報 クラス名などが分かります オブジェクトの数量や
平均サイズや 合計サイズなども
小さなオブジェクトも 多数 表示されますが それは問題ではありません
ヒープは数でソートされます 私が見たいのは 数ではなくサイズなので sortBySizeフラグを使い サイズでソートします
NSConcreteDataが巨大ですね 結果はバグレポートにしますが まだ十分ではありません 源を探るのです NSConcreteDataの1つを アドレスを取得します アドレスフラグを使い― クラス名でアドレス指定をすると アドレスが取得できます
データの源が分かりました ここでmallocのスタックログが 役立ちます システムが各配置を記録します ログはmemgraphの記録時に キャプチャされ ツールの出力時に 注釈を付けます diagnosticsタブで設定可能です 私のお薦めはLive Allocationsです
memgraphはmallocスタックログに キャプチャされました 次は配置のバックトレースです 使うのはmalloc historyです memgraphに malloc historyをパス さらにインスタンスの アドレスをパス バックトレースがあれば 表示されます
これはNSConcreteDataの アドレスの1つです malloc historyにパスすると バックトレースがありました NoirFilter.applyメソッドが 巨大なNSデータを生成しています これはバグレポートに添付して 誰かに見てもらいます
以上はAppの動作を調査する ほんの一部の方法です メモリ問題の対処に使うツールは? 3つ考えられます オブジェクトの生成を見る場合 オブジェクトに関連するものを 見る場合 単にサイズを見たい場合
もしプロセス開始時に mallocスタックログが有効なら malloc historyが オブジェクトの バックトレースを見つけます オブジェクト関連を見たいなら leaksやその他のオプションを 使うことができます そしてサイズを確認したいなら vmmapとheapです 私自身はvmmapとsummary flagを 使うことをお薦めします そしてスレッドに従います
ここでカイルが― 最大のオブジェクトである 画像について説明します カイル? (拍手) ありがとう それでは 画像です 画像で最も重要なことは メモリに関連するのはファイルの サイズではなく寸法だということ ここにiPad Appの背景に使いたい 美しい画像があります 寸法は幅が2048ピクセル 高さが1536ピクセル ディスク上で590キロバイトです さて メモリの使用量は?
10メガバイトです とても大きいですね 2048ピクセルと1536ピクセルの 積のサイズがその原因です 1ピクセル4バイトなので 約10メガバイトになります なぜこんなに大きいのか? iOSでの画像の説明をします “Load”と“Decode” そして“Render”があります ロードには590キロに圧縮された JPEGファイルがあります メモリにロードします デコードではJPEGをGPUで 読み込めるフォーマットに変換 この解凍で10メガバイトになります デコードすると レンダーが可能です 画像ついての詳細や 最適化の方法は― “Images and Graphics Best Practice”のセッションをどうぞ
画像はSRGBフォーマットで 表示されます これは最も典型的な 画像のフォーマットです 1ピクセル8ビットです
そのため 赤が1バイト緑が1バイト 青が1バイトそしてその他
さらに大きいのが
iOSのワイドフォーマットです 色が豊かになり 1ピクセルに2バイト必要です サイズが倍になりました iPhone 7 8 Xや iPad Proなどのカメラは― このハイファイコンテンツに 対応しています スポーツロゴなどにも 使える精密さです しかし便利なのは 大きなディスプレイの場合です つまり実際は小さなサイズでも 対応できます ルミナンスと アルファ8フォーマットが これはグレースケールと アルファ値のみで 通常はMetal Appのような シェーダなどで使われます 一般的ではありません さらに小さくなると アルファ8フォーマットです 1チャネルのみで 1ピクセル1バイトです とても小さく SRGBより75%も小さいので マスクやモノクロの テキストなどに最適でしょう 内訳を確認してみると 1ピクセル1バイトから 1ピクセル8バイトまであり 幅広いですね 必要なのは 正しい選択を知ることです 正しいフォーマットを選ぶには?
フォーマットを選ばずに― 選ばせます
もしUIGraphicsBeginImageContext WithOptionsを使っているなら これは初期からあったAPIですが UIGraphicsImageRendererに 変えてください メモリを抑えられますよ UIGraphicsBeginImageContext WithOptionsは1ピクセル4バイト SRGBですね 従ってワイドフォーマットも アルファ8も使えません UIGraphicsImageRendererの APIを使えば APIが自動的に 最適なフォーマットを選びます
これが例です マスク用に円を描きます ハイライトされたdrawing codeを 処理するのは旧型のAPIですが 黒い円を描くのに1ピクセルあたり 4バイトのフォーマットです 新しいAPIに変更して 同様に実行すると 1ピクセルあたり 1バイトになりました 75%のメモリ削減になります すばらしい節約ですね
さらなる利点もあります マスクの再利用時 色の変更が可能です imageviewで複数色に変えられます 更なるメモリは必要ありません 黒の円だけではなく 青や赤や緑の円を描いても 追加メモリは必要ありません
その他の処理と言えば ダウンサンプリングです サムネイル用などに 縮小したい場合 UIImageは使いたくありません もしUIImageを使えば 座標空間の変更が必要なため パフォーマンスが悪くなるうえに メモリ内の画像を 解凍しなければなりません そこでImageIOフレームワークです ImageIOはダウンサンプルが可能で 最終画像にはダーティメモリの コストしか必要ないため メモリ使用量の上昇を防ぎます
例えば ディスクからファイルを 取得した際のコードです ダウンロードした ファイルでも可です そしてUIImageを使い 小さい長方形を描きます メモリ使用量が上昇します 今度はImageIOを使い ディスクから ファイルをロードします 画像の大きさを入力する パラメータをセットします そしてCGImageSourceCreate ThumbnailAtIndexを使い作成します CG画像は UIImageでも作れます より小さな画像を 以前の半分の時間で手にしました
次のトピックはバックグラウンドで 使用する場合の最適化です フルスクリーンのAppの画像が あるとします とても美しい画像です しかし通知を確認する際などに ホームスクリーンに戻ります 画像はメモリの中です 今までの経験から“表示されない 大きなリソースはアンロード”です
方法は2つあります まずはApp lifecycleです バックグラウンドでも フォアグラウンドでも使用できます UIViewControllerに準拠しておらず オンスクリーンでの適用です UIViewControllerのメソッドは― 複数view controllersがありますが スクリーンに 表示されるのは1枚です viewWillAppearや viewDidDisappearを活用して メモリフットプリントを 小さくできます これが例です バックグラウンドにある Appに通知をセットします この場合は 画像のアンロードをセットします Appがフォアグラウンドに来ると 通知されます 画像をリロードしても バックグラウンドではメモリを セーブでき 忠実性も維持できます システムに メモリの余裕ができます
Navigation Controllerや Tab Bar Controllerと同様に ViewControllerは 画像が消えるとアンロードします そしてviewWillAppearで戻る前に リロードできるので ユーザは違いを感じませんが メモリの使用量は減少します
ここからは クリスにデモをお願いしましょう クリス? (拍手) デモマシーンに切り替えます 始めます 使うのはこのAppです まずはこの高解像度の画像から 始めたいと思います NASAの太陽系の写真に 様々なフィルタを適用します 簡単な例として 太陽に フィルタを適用するのが見えます これまでの結果に 満足しています 私がジェイムスに意見を求めると 返信メールに ファイルが2つ添付されていました 1つはmemgraphファイルで もう1つはこの画像でした
ジェイムスは とても控えめな男性なので こんな絵文字を送るなんて 相当 混乱しているようです 私は彼に“何が問題か分からない” “赤い部分に達してないから まだ500メガバイトはあるはずだ” “使っていいだろう?”と 聞きました そして優秀なジェイムスは 私の考え方の― 間違いを指摘しました まず このゲージは2ギガバイトの デバイスの測定結果です それ以下のメモリの デバイスもあります 1ギガバイトのデバイスで 同じコードを実行したら AppはOSから 強制終了させられるでしょう 第2に OSはAppを強制終了する際 今使用しているAppだけでなく 他に実行されているAppや OSのメモリも考慮します 従って 赤い部分に達してなくても 強制終了の危険性はあります 第3にユーザにとっては 最悪の例ですが 使用量の比較を 示したチャートに― ゼロキロバイトの メモリが存在します これはAppのスペースのために OSから放棄されたメモリです ここで私をにらんでくださいね ユーザは他のAppを使う際 再ロードする必要があるからです
ジェイムスは正しいです この針が示すメモリ使用量を 最大限小さくするために― 何ができるでしょう memgraphファイルを見ます
memgraphを使う時に―
私にはいくつかの戦略があります 第1に… スクリーン表示を調整します まずリークを探します フィルタツールバーに行き leaksフィルタをクリックします memgraphにある リークが表示されます このファイルにはありませんね これは良いニュースであり 悪いニュースでもあります 何が起きているのか 調べる必要があります
memgraphのいいところは― 予測以上のインスタンス数を 表示してくれるところです このmemgraphを使って コードから特定のオブジェクトに 絞ると メモリには5つしかなく 実際は 1つずつなのが分かります 複数のrootViewControllerや 複数のnoirFilter multiple filtersなどがあれば 予想以上に 調べられることは多いでしょう 予測以上の インスタンスはないようです 1つのサイズが大きいので 念のため調べてみましょう メモリインスペクタを使い 確認します オブジェクトの サイズがリスト化されます 私のAppは32バイトです viewcontrollerのデータは1500 よく見てみても ここにあるどのオブジェクトも Appが1ギガを超えている― 原因ではなさそうに思えます これがXcodeで memgraphを扱う方法です 次はどうしましょう? WWDCセッションで学んだ― コマンドラインツールを使います 何か見つかるか試してみましょう ジェイムスのお薦めは― summary flagと vmmapを使うことでした 私のmemgraphファイルで 試してみましょう
出力を確認します ここで私が探すべきは? 普通は大きな数字ですね メモリの使用量が多い 何かを見つけるのです 多くのカラムが存在しますね 重要なものもあります まずは現実ではない 仮想サイズですが このカラムは無視します Appが要求したメモリですが 今は使用されていません ダーティは響きが悪いので 使用したくありませんね 私は自分のAppは クリーンなほうがいいです そして iOSなので 圧縮物を探します カイルとジェイムスが話したとおり ダーティなサイズと 圧縮されたサイズの合計が OSがAppのために 要求するメモリの容量です これら2つのカラムに注目し 大きな数値を探します CG画像がすぐ目に入りました ダーティなサイズと 圧縮されたサイズです 今はとにかく探し続けます IOSurfaceに大きな ダーティサイズがあります 圧縮されたサイズはありません MALLOC LARGEにも ダーティサイズがありますね 圧縮されたサイズはとても少量です 他にはそれほど 大きなサイズのものはありません ここで見る限りでは 仮想領域のCG画像に 集中していくべきですね それを理解した上で進みましょう 次のステップは? 仮想メモリについて知りたいですね vmmapに行きましょう 今回はsummay flagではなく Memgraghファイルを実行します 心配なのはCG画像です vmmapが示す他の仮想メモリ領域は 気にしません グレップを使いましょう CG画像についての ラインが見たいです どうでしょうか? ラインは3つあり 2つの仮想メモリ領域が 存在します それから 開始と終了のアドレスがあります 上記と同じカラムと分かります 仮想でかつ常駐 ダーティで圧縮されたラインです 最後のラインは サマリーラインなので
上のラインと同じですね 2つの領域のうち1つは小さく 片方は巨大です 私はこの巨大な領域を 見たかったのです
この領域について より詳細を知るためには? vmmapについての 書類を見たところ verboseフラグに気づきました このverboseフラグは 多くの情報を出力します 何が分かるでしょうか? verboseとmemgraphを 実行してみましょう
再度言いますが 私が注視するのはCG画像の領域のみ グレップを使いフィルタをかけます 多くの領域が現れました どうでしょうか? さてvmmapは初期設定で― 連続した領域が見つかると 一緒に折り畳みます 2ライン目を見てみましょう この領域の 終了と開始のアドレスが同じです 同じパターンが続いているので― vmmapがそれらを 単一の領域に変えます いくつかの違いにも気づきます ダーティなメモリを さらに使用するものもあり 圧縮されたメモリもある 注目すべきものが分かりました しかし別の方法を使います 必須ではないですが 一般的なルールとして 仮想メモリ領域作成が遅れると Appの作動も遅くなります このmemgraphはメモリ使用量の 上昇中に取得されたので これらの領域は メモリの上昇と関連しています 最大のダーティや圧縮サイズでなく 領域の最後から探ってみます
最終領域の最初のアドレスを取得し 次にすることは? ジェイムスが先に述べた ヒープがありますが ただし仮想メモリ領域では 役に立ちません 試したいリークは存在せず Memgraphにも見当たらないので leaksは使えません leaksに関するヘルプ情報を 調べてみました leaksでできることは多く ヒープ上や仮想メモリ領域の 関連オブジェクトも― 示してくれます 進めてみましょう leaksを使いtraceTreeフラグに パスしてみます これで関連するアドレスの― ツリービューが表示されます ここで私の仮想メモリ領域の― 開始のアドレスをパスします 最後にmemgraphファイルを 提供します どうなるでしょう? 関連するツリーを見てみましょう スクロールをして 上を見てみます 仮想メモリ領域と CG画像領域があります そしてツリービューがあります 関連するものが すべて表示されています ここでXcodeに戻り― 同じアドレスでフィルタをかけます
そしてオブジェクトを見ると ツリーはリークスから得たものと 全く同じもので ノードの1つ1つを 展開することも可能です 詳細を見てみましょう 少し時間がかかるし退屈です leaksの出力で良い点は スキャンや検索や フィルタが素早くできること バグレポートや Eメールに挿入可能なことです Xcodeのグラフィカルビューでは 不可能です 何が見つかるでしょう? 理想的には 私のAppのクラスを 見つけたいですね ですがクラスがないことは 確認済みです さてどうしましょう? フレームワークのようなクラスは 代理もしくは 直接作ったものです 私のAppにはUiviewがあり UIImageがあります そしてコアイメージクラスを フィルタリングに使っています 先に進みましょう 洗練されたデバッグツールを 調べてみましょう さらに見ていきますよ
欲しいものがあるでしょうか
大きな出力です 少し混乱しますね 例えばフォントに関連するものです 私のAppで使っています フォントは メモリを 大量には消費しませんので 意味がありません 下部を見てみると CIクラスが 多数表示されています これはコアイメージフィルタなどで Appのフィルタ機能を作成します これも確認しておきたいことです しかし既に確認し 何も見つかりませんでした
従ってleaksの出力は 必要ありませんね 残念でした 次はどうしましょう?
幸いにもジェイムスには メモリバックトレースがありました memgraphをキャプチャした際に 記録したものです 従って別のツールを使います それでオブジェクトの バックトレースを見ましょう malloc historyを使います
まずmemgraphファイルにパスします
ヘルプ文書から fullStacksフラグにパスします そして人間が読みやすいように 出力します 次に仮想メモリ領域の 開始アドレスにパスをして 様子を見ます バックトレースは大きくないので コードがラインに表示されています ライン6から9は 私のAppのコードです ライン6にあるのは NoirFilter apply関数で 特定の仮想メモリ領域作成の 元になっています 私のAppでどの部分がメモリを 使用しているかが分かる証拠です Memgraphファイルに戻ると Xcodeと同じバックトレースが 見て取れます NoirFilter applyメソッドも あります 実際にデバッグしていないので ハイライトは見られません memgraphファイルを 読み込んでいるので malloc historyと 同じ出力ですね そして実際に さらに確認をしてみると 仮想メモリ領域の CG画像のフルリストがあります 下から2番目の行をクリックして 移動させます バックトレースを見ましょう
同じバックトレースですね 同じコードパスが その領域にもあります いくつかの領域を見てみると― バックトレースは同じでした さて これで アプリケーションのどの部分が 仮想メモリ領域を作成する原因と なるのかが分かりました どうしましょうか? Xcodeに戻り memgraphファイルを閉じます 私が最初にしたいことは このコードの中の― フィルタを見ると
apply関数があるのが分かります さらに何かが表示されています UIGraphicsBeginImage ContextWithOptionsや― UIGraphicsEnd ImageContextよりも この状況に最適なAPIがあります まずは基準値を 設定する必要があります Appのメモリ消費に どれほど 影響があるか確かめるためです Appを実行してみます そしてデバッグナビゲータで メモリレポートを確認します 私のAppのメモリ使用量を 見てみましょう 私はこの土星の北極の 画像が大好きです 奇妙な六角形が クールで 少し不気味です フィルタを適用して 結果を見ましょう 1ギガ 3ギガ 4ギガ 6ギガ 7ギガ 最悪です
しかしこれはデバイスでは 使い物になりません シミュレータ実行時は デバッグやテストに役に立ちます デバイス上でも確認しましょう シミュレータは決して メモリ不足になりません Appが強制終了された時は シミュレータを使ってみてください 割り当てができるので 強制終了されることはありません それで調査もできます ここに表示されている グラフに注目したいと思います
7.7ギガバイトに達しています ひどいですね さて 何ができるでしょう?
ここでapply関数に戻ります beginImageContextWith Optionsに戻りますが カイルの言葉を思い出します 画像を扱っている時 メモリに関して最も重要なことは? 画像のサイズです それでは見てみましょう 再度フィルタを挿入します
デバッガで停止したら
画像のサイズを確認します そして戻ってくる前に 水を飲みます やはり飲むのはやめました
これは1万5000と1万3000の積です 文書を見ると UIImageでは ピクセルではなくポイントでした 2Xもしくは3Xのデバイスであれば さらに大きい数を 掛ける必要があります カイルは10メガバイトで 驚いていました これは彼には秘密です 試したいことがあります やってみます
1万5000と1万3000の積として iPhone Xは3Xデバイスなので 幅3を掛けます
さらに高さ3を掛け 1ピクセル4バイトも掛けます
また大きな数字ですね
メモリの内7.5ギガバイツほどを 使用しているということです
原因はbeginImageContextではなく 画像サイズでした 画像は大きくなくてよいです 自分のビューと同じ寸法へ 縮小したいです メモリの削減にもなります 画像をロードするコードに 戻ります その前にブレークポイントを 無効にします
どうなるか見てみましょう 簡単ですね バンドルからURLを取得し URLのデータから UIImageにロードしています それがフィルタにパスされます フィルタに渡る前に 画像を縮小したいです カイルの言っていたように UIImageは使いません 画像全体をメモリに 読む込むことは避けたいからです この関数は使わないでおきます カイルの提案どおりの コードに置き換えます
すると どうなるでしょう? バンドルから画像を取得します 今回は― 少し幅を広げて… CGImageSourceCreateWithURLを 呼び出します それからCGImageSourceCreate ThumbnailAtIndexにパスし 全体をメモリに読み込まずとも サイズが変更できます 違いを確認しましょう 再構築します
そしてAppの起動を待ちます 一旦表示されると… おっと 警告が出ました
この部分を省きます どうなるでしょう 作っています 作成中です 出来上がりました メモリレポートを確認し 土星の北極の画像に戻りましょう これが ずっと言いたかったんです 画像がどうなるか見てみましょう 75…93メガバイトになりました グラフの最高点は93メガバイトです 著しく向上しました (拍手) シャットダウン寸前の 7.5ギガバイトからの改善です ここで戻りたいと思います そして停止させます フィルタメソッドに戻り
UIBeginImageContextを カイルの提案どおりに変更します このコードを消すことにして 新たなフィルタを追加します そしてUIGraphicsImageRendererを 使います そしてこのRenderer内で フィルタを適用するため CIFilterを使います
実行します 願わくは― メモリ使用量の違いが 現れますように
デバッグナビゲータと メモリレポートに戻りましょう そして再び 土星の画像に戻ります
ここでフィルタを適用させます これでグラフがどうなるか? 98メガバイトですね 先ほどと ほぼ同じですね 私が期待したとおりです 画像は1ピクセルあたり 4バイトを使用しています ここではメモリ削減が 目標ではありません しかし削減のチャンスはあります 例えばOSがピクセルあたりの バイト数を少なくしたり さらなるバイト数を要求しても 問題はないです 大きな向上はないですが 私のコードは改善しました まだできることがあります Appがバックグラウンドに入った時 画像をアンロードし スクリーンにない画像はビューに 表示しないようにするなどです しかし この結果に 本当に満足しています ジェイムスに返送しましょう スクリーンショットと共に 注意書きを付けます 私の喜びを知ってもらいたいのです 私が彼に送るのは―
目が星の絵文字です ジェイムスが 喜んでくれると願います では最後を締めてくれる カイルを呼びましょう ありがとうございました (拍手) ありがとう クリス
ありがとう すばらしいですね 少しの作業でメモリ使用量を 大幅に削減できました
まとめるとメモリとは 有限で共有物だということ 個人が使えば他人は使えない メモリは必要な分だけを使う 心がけが必要です
デバッグの際は Xcodeのメモリレポートが重要です Appの実行時に Xcodeも実行すれば デバッグで不具合に 気づくことができるでしょう
iOSは画像フォーマットを 選択します SRGBからアルファ8で 75%のメモリを節約できたのは UIImageGraphicsRendererの 使用によるものです マスクやテキストに最適です
画像をダウンサンプリングする際は ImageIOを使用します メモリの急上昇を避け UIImageでの 描画よりも高速です
画面上にない画像やリソースを アンロードしたいのです ユーザが見えないもので メモリを消費するのは無意味です
これだけやっても まだ足りません Memgraphの使用は メモリフットプリントの 削減と状況把握に役立ちます malloc historyとの組み合わせで メモリの状態が把握できました 従って 私が皆さんにお薦めするのは malloc historyを使い ツールを分析することです
詳細はスライドをご参照ください そしてさらに 質問がある方はこの後 ラボにお越しください WWDCへの ご参加ありがとうございます (拍手)
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。