ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Appのストレージを最適化する
Appにデータを保存する方法は、ディスク使用量だけでなく、Appのパフォーマンスとデバイスのバッテリー駆動時間にも影響します。このセッションでは、データのシリアル化を最適化し、画像を処理し、ディスクと同期するテクニックを説明し、SQLiteの機能を活用してパフォーマンスと安全性を向上させる方法を紹介します。
リソース
関連ビデオ
WWDC21
WWDC19
-
ダウンロード
(音楽)
(拍手) 今日は僕 カイ・カハイナと 同僚のアレジャンドロが Appのストレージの最適化を お話しします CPUメモリ同様 ストレージも有限リソースです ストレージの最適化には メリットがあります バッテリー寿命の延び パフォーマンス向上
フットプリントの減少 デバイスの正常性
今日の主要トピックは 効率的な画像アセット ディスクへの同期 シリアル化されたデータファイル Core Data そしてSQLiteです
まずは効率的な画像アセット 画面サイズと比例して 画像アセットも大きくなります
今回のセッション用に作った 簡単なデモ用Appですが
数枚の写真をロードしただけで 24.6メガバイトあります サイズを減らす2つの方法があります
まずHEICです HEICはHEIFとも呼ばれ JPEGより効率的で優れた形式です
同品質で比較すると HEICはJPEGより50%小さいです つまりオンディスクフットプリントが 小さく アップロードやダウンロードが 容易になります
ロードとセーブも早くなります
JPEGにない多くの特徴があり 深度と視差情報を含む サブ画像を保存できます
またアルファや 可逆圧縮をサポートします
さらに1つのコンテナに 複数の画像を格納できます
HEICはiOS 11とmacOS High Sierraから サポートされており 他のオペレーティングシステムにも 対応します
デモ用Appです JPEGアセットの24.6メガバイトの オンディスクフットプリントを―
HEICに変えることで 17.9メガバイトに減らせます これだけで27%の減少になります
もう1つの方法はアセットカタログ Appのアセットを整理するのに いい方法です 例えば Appアイコンや
デバイスやスケールバリアントなどです 複数の解像度の同一画像を 簡単に保存できるので 多様なデバイスを 簡単にサポートできます
アセットカタログは オンデマンドリソースのタグにも使え 使用するまではダウンロードが不要です
ストレージとパフォーマンスに メリットがあります
1つのフォーマットで 画像アセットを保存するため オンディスクフットプリントが 減らせます
アセットカタログのメタデータは Appスライシングにも利用されます Appのダウンロード時に 必要なアセットだけ取得できます ダウンロードの待ち時間が減り ユーザはAppを早く楽しめます
パフォーマンスも向上します 最適化されたフォーマットで セーブすることで 画像のロードや Appの起動も早くなります Macでは起動時間に 最高10%の改善が見られました
さらにGPUベースの圧縮も 容易にできます デフォルトは可逆圧縮ですが 不可逆圧縮も可能です
ハードウェアアクセラレーションによる 解凍や インメモリーフットプリントの 削減もできます
デモ用AppはHEICでサイズが 17.9メガバイトに減りました アセットカタログを使うことで Appスライシング機能により フットプリントを 14.9メガバイトに減らせます HEICとアセットカタログを使い 40%減少しました
まだお使いでない方は これらを ぜひお試しください フットプリントを簡単に 効率化し小さくできます
次はファイルシステムメタデータです
最後に起動した時間を記録する Plistがあります Appの起動時 Plistを読み込み 最終起動時間を更新し Plistをディスクに戻します
File Activity Instrumentは 後ほどアレジャンドロが説明します 当然 Plistの読み込みは1回ですが 驚くべきことに 書き込みは3回行われます
fsyncオペレーションは高負荷です その理由は あとでご説明します
書き込みが3回なのは ファイルシステムメタデータのためです データの作成や削除のたび ファイルシステムが更新されます Plistの書き込みやファイル作成は オーバーヘッドとなります
ファイルシステムメタデータとは? ファイルのトラッキングに 必要な情報です 例えばファイル名やサイズ ファイルの場所や 作成日または最終変更日などです もっとあります
ファイル作成時の ファイルシステムを理解するため NSDictionaryファイルを書き込む際の APFSを見ます
最初にAPFSは ファイルシステムツリーを更新します これはノードの更新で行われます しかしAPFSのコピーする性質により 既存のノードを更新せず コピーを作成します 詳しく見ましょう 既存のファイルシステムツリーです
ノードのコピーが作られました
新しいノードは元のノードと 名前は同じですが トランザクションIDが異なります これでAPFSはスナップショットを サポートできます
ファイルシステムツリーは 更新されましたが オブジェクトマップの更新が必要です 書き込みをサポートするには 2つのオペレーションが必要です Write I/Oは合計で8Kです
ファイルシステムツリーの更新で4K オブジェクトマップ更新で4K ファイル自体も書き込みます NSDictionaryは240バイトですが 書き込める最小ファイルの 4Kに繰り上げられます
240バイトのNSDictionaryの保存に 合計12K必要です 効率は2%です 以上の例で APFSの働きと共に 小さなデータの保存が高負荷なことが お分かりいただけたでしょう
この例では ファイル作成に 8K必要なことが分かりました
ファイルの削除も8K必要です 他にも ファイル名の変更に16K 既存ファイルの修正に8K必要です
ファイルシステムの更新は有料です 保存されるデータより I/O使用が多くなるため 作成や削除するファイルを 選ばねばなりません 急速なファイル作成や削除は 避けたいです
システム上に一時データを置く場合 コストを下げるいい方法があります まず ファイルを作成します
リンクせずオープンにし fsyncを呼び出しません 一時ファイルをできるだけ長く OSキャッシュにとどめ 頻繁なディスクへの書き込みを 少なくします
APFSの ファイルシステムメタデータについて 詳細はDeveloper Webサイトを 見てください
次にディスクへの同期です
パフォーマンス的には データはCPUに近いキャッシュに 置きたいです しかし必要な時にデータを ディスクに動かす必要もあります キャッシュの種類を説明します
パフォーマンスのために データをOSキャッシュに置きます OSキャッシュへの読み書きは 論理的I/Oで メモリのバックアップにより 素早く完了します 頻繁に使用や修正するデータに 適しています
ディスクキャッシュは物理的に ストレージデバイス上にあります
最後は永続ストレージ データを長く保持するための 物理的媒体です iOSとほとんどのMacではNANDです
物理的I/Oは物理的ストレージや ディスクキャッシュや永続ストレージに 読み書きします
キャッシュや永続ストレージの 説明でした キャッシュからストレージへの データ移動のAPIを説明します
まずfsync
ディスクキャッシュに データを移動させます
すぐには永続ストレージに 書き込まれません 永続ストレージに移動させるかどうか デバイスのファームウェアが決めます 書き込み順序も保証しません データの順序がOSキャッシュから ディスクキャッシュへと ディスクキャッシュから 永続ストレージとで違うかもしれません
fsyncも多用すると高負荷です OSキャッシュにデータがあると 同じデータへの 上書きや修正を簡単に吸収できます fsyncで ディスクキャッシュに移動させました ディスクキャッシュへの 移動の頻度次第で マニュアルでのfsyncの呼び出しが 不要なこともあります
OSキャッシュから永続ストレージへの データ移動は 主にF FULLFSYNCを使います これはディスクキャッシュ内のデータを すべてフラッシュします 移動させたいデータだけでなく ディスクキャッシュの全データが 移動されるのです 結果的に高負荷になり時間もかかります
これもOSが定期的に行うので マニュアル操作が不要なこともあります
I/Oの順序を確保するため F FULLFSYNCを使いますが F BARRIERFSYNCの方が効率的です こちらはI/Oを順序化します F BARRIERFSYNCは バリアを持つfsyncだと考えてください バリア後のiOSの実行前に バリア前に受け取ったiOSを 実行するかApple SSDに示唆します
F FULLFSYNCより安くなるのは ディスクキャッシュ内の全部のデータを 移動させないからです
F BARRIERFSYNCは F FULLFSYNCより 早く効率的に データをディスクに移動します
シリアル化されたデータファイル PlistやXML JSONは 扱いやすいファイルです 頻繁に修正しないデータの保存に最適で パースも簡単です しかし短所もあります
変更のたび 全ファイルが 再シリアル化または再書き込みされ 増減が不完全になります 使用の容易さから誤用も多いです 変更のたびファイルを置換するので メタデータインテンシブです
データベースの置換には不向きです
File Activity Instrumentです Plistの作成 読み取り 修正で I/Oオペレーションが12回 4行のコードにしては多いです
アトミックな書き込みのため NSDictionaryを呼び出すたび― fsyncオペレーションで終わります NSDictionary内の移動させたいデータは OSキャッシュの恩恵を受けません
大きいか修正が頻繁なデータセットは シリアル化されたPlistでは非効率です
膨大または更新が頻繁なデータには Core Dataがいいでしょう
Core Dataは抽象化レイヤを SQLiteデータソースと Appのモデルレイヤ間に作ります
自動的なオブジェクトグラフの管理
トラッキングと通知の変更
自動バージョントラッキングと マルチライタの競合解消を行い 複数の同時オペレーションの接続を プールします
またCore DataはiOS 13から CloudKitインテグレーションを サポートします
ライブクエリをサポートし オンザフライクエリを生成できるので 事前のSQLiteクエリの 手動コーディングが不要です
さらに自動メモリ管理
ステートメントアグリゲーションと トランザクション
スキーママイグレーション
iOS 13からデータの非正規化などを サポートしています
Core Dataの採用で モデルレイヤのコードが 50~70%減らせます 書き込み 修正 デバッグが不要です SQLiteを直接利用する際の ベストプラクティスがあります
サブトピックに分けて説明します 接続 ジャーナリング トランザクション ファイルサイズ プライバシー パーシャルインデックス
まず 接続です
SQLiteの堅牢性の保証は有料で データベースの開閉は 高負荷なオペレーションです 整合性チェック ジャーナルのリカバリや チェックポインティングなどです
そのため従来モデルより 我々が推奨しているのは データベースをオープンにし 必要時のみ接続を閉じる方法です マルチプロセススレッドでは スレッドが必要とする限り オープンにします これはデータベースのコスト償却に 役立ちます
次はジャーナリング
デフォルトの Delete Mode Journalingは 最も効率的とは言えません Delete Mode Journalingを 見ていきましょう
データベースを4ページ修正するなら
まず 4ページを ジャーナルファイルにコピーします
次にデータベース内の 4ページを修正します 終わるとジャーナルファイルは 削除されます 修正したいページ数の2倍に 書き込まねばならず すぐに削除されるジャーナルファイルの オーバーヘッドも必要です
幸い より効率的な方法があります Write Ahead Log またはWAL Mode Journalingで 書き込みを減らすことができます 書き込みを同じページに結合し バリアの使用を減らします 複数のリーダとライタと スナップショットもサポートします 詳しく見ていきましょう データベースを4ページ修正します データベース内ではなく Write Ahead Logファイルに書き込みます 追加のトランザクションがあり Write Ahead Logファイルに ページが追加されます チェックポイントに届くまで続きます WAL Modeの大きな利点の1つです Delete Modeでは 複数回 修正されるのに対し 同一ページへの変更がマージされます 同じページの書き出しは1回です
終了後 今後のために WALファイルのヘッダを上書きし Write Ahead Logファイルの 維持コストを減らします
WAL Modeは ほとんどのSQLite利用で有効です まだお使いでないならば 切替をお勧めします Appのパフォーマンスが 大きく向上します 複数のINSERT UPDATE DELETE ステートメントを使い SQLiteに情報を与え 効率化を可能にします 複数のステートメントで 複数回 変更されるページが 1回だけ書き出されます
3つの各トランザクションには 同じページを修正する ステートメントがあります 同じページが3回修正されます
しかしシングルトランザクションに 3つのステートメントがあれば 変更は結合され 1回の書き出しで済みます
シングルトランザクションの マルチステートメントは 変更の集約に適しています
ファイルサイズとプライバシー
データベースからデータを消すと? データがあったスペースは フリーになりますが データはまだディスク上に存在します 効率的にセキュアに 機密情報を削除するには―
secure delete=FASTを使うことを お勧めします
削除データを自動的にゼロにし ヘッダとして同じページにあった データのコストがかかりません iOS 13のSQLiteではデフォルトです iOSの古いビルドの場合 secure delete=FAST Pragmaを 指定してください
サイズ管理に関しては VACUUMの使用はお勧めしません
とても遅くI/Oインテンシブです SQLiteデータベース上の VACUUMの働きを見ましょう
フリーページをすべてVACUUMします
キャッシュを開き ジャーナルファイルを作成 データベースからジャーナルファイルへ SQLiteをダンプし すべての有効データをコピーします チェックポイントの際 データベースは ジャーナルファイルのサイズに トランケートされます データがジャーナルから データベースに再挿入されます 完了後 ジャーナルファイルは 破棄されます
これはかなり高負荷です ジャーナルファイル 再度 データベースへと 全部の有効データが 2回書かれるからです
メモリに対し データベースが大きすぎると SQLiteはスピルファイルを使い 終了まで余分なデータを管理します
幸い 効率的な代替方法があります auto vacuum=INCREMENTALです データベースファイルのサイズを 効率的に管理できるだけなく Vacuum処理したいページを指定できます フリーページを 残しておくこともできます
詳しく見ていきましょう 例として2ページ Vacuumします 前の方法は 全部のデータを移動させました
この方法は最後の2ページを Write Ahead Logにマイグレートし 修正される親ノードも Write Ahead Logに書き込まれます またLogのチェックポイント時 データベースファイルが トランケートされます マイグレートしたページが フリーページだった場所に移り 更新済みの親ノードが書き込まれます データベースの全データは動かさず ページのサブセットだけを修正するので 非常に効率的です
auto vacuum=INCREMENTALを 使用すれば セキュアな削除でファイルサイズと プライバシーが管理できます
パーシャルインデックス
インデックスはORDER BYや GROUP BYWHERE節より便利ですが 有料です ある程度のオーバーヘッドがかかります インデックス追加後 データベースへの書き込みは 高負荷になります
効率的な代替方法があります パーシャルインデックスです インデックスが必要な時に WHERE節を使い 必要な時だけ インデックスを利用できます 無駄にインデックスの価格を 払う必要はありません
SQLiteのまとめです
データベース接続は長くオープンに
Delete Mode Journalingではなく WAL Journaling Modeを使う
トランザクションごとに マルチステートメントを使う
セキュアな削除と auto vacuum=INCREMENTALで ファイルサイズとプライバシーを管理する 通常のインデックスではなく パーシャルインデックスを使う
こうした点に悩みたくない方には Core Dataをお勧めします Core Dataが全部やってくれます Core Dataは改善を続けており 無料で利用できます
次はアレジャンドロが File Activity Instrumentと I/Oの最適化の実例をお見せします アレジャンドロ (拍手)
ありがとう カイ おはよう 皆さん セッションに来てくださってありがとう File Activity Instrumentの 改善を ストレージの最適化に どう利用するかお話します File Activity Instrumentは どう変わったでしょう
まず すべてのAppleデバイスに サポートを追加 同じプロファイル エクスペリエンスが iOSデバイス Mac Apple Watch Apple TVなどで可能になりました
また自分のAppだけでなく システム全体を追跡できます そのためI/Oサブシステムに関する Appの動きや システムの他との インタラクトも見られます
論理的I/Oと物理的I/Oには 違いがあります どうインタラクトするか理解するのは I/O利用に非常に重要です File Activity Instrumentで 両方が見られます
最後に 自動推論もサポートします これはメカニズムがさまざまなので 簡単に説明します
追加された自動推論に基づく アンチパターン検出は 過剰な物理的書き込みの検出です 追跡中にFile Activity Instrumentは Appの過剰活動を検知し通知します それにより原因を調べることができます
また今回追加されたサポートは フレームワークの失敗時の I/O関連システムの呼び出しです Appの正当性を 理解する上でも重要です
fsyncとF FULLFSYNCについては カイがお話しました 準最適キャッシングの検知もあります これで Appが OSキャッシュを 最大活用していない時が分かります File Activity Instrumentを 見ていきましょう
Instrumentを開いたところです File Activityのアイコンは ほぼ同じですが 何をするかの概略が 下に書かれています File Activityを選ぶと トラックが現れます 最上段がFilesystem Suggestions アンチパターンや自動推論の提案が ここです その下はFilesystem Activity 論理的読み込みと 論理的書き込みに分かれます オペレーションが起こった回数を 知らせます さらにもっと深く 呼び出し回数や発信元なども分かります さらにその下のDisk Usageは 物理的レイヤの読み込みと書き込みに 関するものです Disk I/O Latencyは 物理的I/Oが要した時間が分かります デモ用Appで見ていきましょう 写真をお気に入りに入れます 各画像の右下の角に星印が付いています 星印を選択し 画像をお気に入りに入れます バナナナメクジをお気に入りにします
そして摩天楼も 最初のアプローチは お気に入りのたびに データベースを開閉する方法です ベストな方法ではありませんが これを基準にして File Activity Instrumentを見ます
データベースの開閉と同じ作業量で File Activity Instrumentを 実行しました まず注目するのはDisk Usage 物理的I/O情報があるところです 物理的書き込みの列または行に それぞれ物理的I/O量が見えます ツールチップテキストに 54と表示されていますが Detail Viewには 統計が表示されています どんなことが分かるのでしょう
ズームして見ます オペレーションごとに データベースを開閉する場合 1002回の物理的I/Oで 合計フットプリントは約6メガバイト これだけではよく分かりませんが 他と比較すれば よくないと分かります レイテンシなど他の統計もありますが 最初の概要に切り替えます Filesystem Suggestionsトラックの Instrumentの通知です ここも詳しく見てみましょう 同様に下にあるDetail Viewの Count列で 通知が12回あったことが分かります ドロップダウンして見ると 通知が過剰な物理的書き込みだと 分かります 効率的な方法でないと分かっているので 他の方法を試します ともかく12回の通知を基準としましょう
最後は Filesystem Activityトラック どのオペレーションが 呼び出されたかなど以外に Appが行った論理的I/Oを 違う角度で見られます デフォルトは ファイルシステム統計ですが ファイルディスクリプタ情報も 見られます
次はデータベースを 必要に応じて開閉する方法です Disk UsageのDetail Viewです 最初に気づくのは 物理的I/Oオペレーションが 1002から54に減ったことです ディスクフットプリントは 約6メガバイトから288キロバイトに かなりの効果ですが データベースを必要に応じ 開閉する方法に変えただけです
Filesystem Suggestionsを 見ましょう 基準とする前の方法では12回でしたが 3回に減りました 理想的ではなくても前進です ジャーナリングなど異なる動作で どう変わるか― Delete Mode Journalingから 見ましょう これはSQLiteのデフォルトです 従って この統計ページは 先ほどお見せしたのと まったく同じです 詳細は割愛しますが 数字を思い出してください 物理的I/Oオペレーションが54回 合計288キロバイトでした
WAL Mode Journalingを使うと 興味深いことが分かります
まずFilesystem Suggestionsトラックで 数値がゼロです 最初が12 次に3 今回がゼロです WAL Mode Journalingを使うだけで 大きな進歩です 次に論理的I/Oと物理的I/Oの インタラクションです Filesystem Activityトラックを見ると 多数の論理的I/Oに対し Appの追跡を止めるまで 物理的I/Oは起きません なぜ重要か? Delete Mode Journalingと 比較しましょう Filesystem Activityを見ると 各論理的I/Oに対応して 物理的I/Oがついてきます 一方 WAL Mode Journalingは OSキャッシュを有効利用しています
Filesystem Activityトラックに戻り Detail Viewを見ます WAL Mode Journalingでは fsync呼び出しは1回 Delete Mode Journalingでは 最高16回 WAL Modeが多く OSキャッシュを使っています
次は写真の削除についてです Appから写真を取り除く方法を お話しします 最初はシングルステートメント トランザクションです 削除したい写真の 左下角のチェックマークを選択し データベースに DELETEクエリを発行します シングルステートメント トランザクションで 写真ごとにDELETEクエリを実行し 結合していないからです
このFilesystem Activityと Disk Usageを見てみましょう ファイルシステムのオペレーション数は 111ですね
Disk Usageを見ると 書き込みが12回 フットプリントが合計72キロバイトです
次はマルチステートメント トランザクションです 写真を削除する際 論理的な観点からも 1枚ずつ削除するよりも 同時に全部削除したいと思います SQLiteではアナログで 1つのトランザクションに 削除ステートメントをマージします つまり シングルトランザクションの マルチステートメントです オペレーションが37に減り かなり少ない作業で済みます Disk Usageについては 書き込みは合計4回に減少 フットプリントは24キロバイトに
並べて比較しましょう シングルステートメントでは Filesystem Activityは111 マルチステートメントでは 37に減りました 実際の物理的なディスク使用状況に 換算します 12回の書き込み 合計72キロバイトが マルチステートメントでは 4回 24キロバイトに減少しました
Vacuumについても説明します データベースを圧縮または 小さくしたいからです まず Full Vacuum 削除のたび Vacuumステートメントを発行します 最適な方法ではありませんが File Activity Instrumentで 見てみましょう Full Vacuumの発行で 合計27回のI/Oオペレーションで 168キロバイトです
Incremental Vacuumに変えると 数値が変わります 合計12回のI/Oオペレーションで フットプリントが72キロバイトです
また並べてみましょう Incremental Vacuumに変えて I/Oオペレーションが27から12へ フットプリントが168キロバイトから 72キロバイトに減りました 大きな効果を得られたことが分かります
では まとめです 学んだことを使ってください Core Data SQLite Plist… 使うのは何でも構いません ただしFile Activity Instrumentで 検証してください File Activity Instrumentは 証明に役立つかもしれません あるいは今まで気づかなかった提案に ビジョンを与えてくれるでしょう
ストレージの最適化を続け 提供されたレッスンやツールを通じて すばらしいAppを作ってください
さらなる情報は パフォーマンスのラボでどうぞ また Making Apps with Core Data セッションは もう少し実戦的な内容です
ありがとうございました (拍手)
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。