iOSアクセラレーションエクスペリエンス





私の名前はMitya Kurkinです。iOSメッセンジャーMail.Ru Groupの開発を率いています。 今日は、iOSでアプリケーションを高速した経験についてお話します。 99%のアプリケーションにとって、高速は非常に重要です。 これは、コンピューティング能力とそれに応じてバッテリー充電が非常に制限されているモバイルプラットフォームで特に当てはまります。 したがって、すべての自尊心のある開発者は、総反応時間を構成するさまざまな遅延を排除するために、アプリケーションの作業を最適化しようとします。



計測

操作を行う前に、現在の状況を修正する必要があります。 つまり、問題領域で現在どのくらいの時間が無駄になっているかを測定します。 測定方法は再現可能である必要があります。そうでない場合、このデータは後続の成果と比較しても意味がありません。 測定方法 状況は異なる場合がありますが、常にストップウォッチがあります。 確かに、これは最も精度の低いオプションです。



プロファイラーを使用して測定できます。 グラフ上に特徴的な領域(不況またはピーク負荷)がある場合は、それらを測定できます。 このオプションを使用すると、より正確な結果が得られます。 さらに、グラフは追加の要因の影響を示します。 たとえば、デバイスのすべてのプロセスの速度を測定する場合、他のアプリケーションが何をしているか、そしてそれが測定結果に影響を与えるかどうかを調べることができます。 プロファイラーに固執するものがない場合は、ログで測定できます。 これにより、より正確な結果が得られる場合がありますが、そのためにはアプリケーションを変更する必要があり、何らかの形でその動作に影響する可能性があります。 この場合も、追加の要因の影響は表示されません。



完璧な結果

特定の状況で加速することが可能かどうかを理解するには、最短時間を事前に理解することをお勧めします。 最も迅速な解決策は、競合アプリケーションで同様の機能の動作を検討することです。 これにより、そのような操作をどれだけ迅速に実行できるかについてのガイダンスを提供できます。 選択したテクノロジが目的の速度を達成できるかどうかを評価する必要があります。 最小限の機能を実行するには、可能なすべてを削除する必要があります。



この場合、目的の速度が得られたら、無効なアイテムをゆっくり返して、これがパフォーマンスにどのように影響するかを確認できます。 記載されているすべての手順を実行した後でも結果が不十分な場合は、使用するライブラリの変更、その品質によるトラフィック量の削減、使用するプロトコルの変更など、より根本的なアクションが必要です。



プロファイラー

最適化を行うと、コードを読むだけで簡単に間違った方向に進む可能性があります。 おそらく、あなたはいくつかの「重い」操作に出くわすでしょう。これは難しいですが、少し最適化できます。 したがって、多くの時間を費やし、最新で最もファッショナブルなアルゴリズムを適用して、成功します。 しかし、同時に、理想は依然として中国以前と同じです。 そして、さらに悪いことかもしれません。 実際、問題は最も予想外の場所にある可能性がありますが、これはまったく疑わしい場所ではありません。 どこかに完全に不必要なアクションが実行されるか、これは単にハングにつながるエラーです。 したがって、最初に何時間を費やしたかを測定する必要があります。 さらに、Appleのツールのおかげで、このような機会があります。







アプリケーションを高速化するには、まずタイムプロファイラーが必要です。 そのインターフェイスは非常に理解しやすいものです。上部にはプロセッサの負荷のグラフがあり、下部にはどのメソッドが使用されたかを示す呼び出しツリーがあります。 ストリーム、フィルター、フラグメント選択、さまざまなソートなどに分割されます。



このデータを最も効果的に使用するには、データの計算方法を理解する必要があります。 次のチャートをご覧ください。







高倍率では、次のようになります。







プロファイラーは、アプリケーションの状態を定期的にポーリングすることで費やした時間を測定します。 そのような測定中にプロセッサを使用する場合、コールスタックのすべてのメソッドはプロセッサ時間を使用します。 そのような測定の合計量によって、費やされた時間を示すコールツリーが得られます。







ツールを使用すると、このような測定の頻度を調整できます。







プロセッサの測定値が測定値に頻繁に記録されるほど、元のチャートのレベルが高くなることがわかりました。 ただし、プロセッサを使用しない場合、この時間は全体の結果に影響しません。 アプリケーションがイベント(たとえば、HTTP要求への応答)を待機している状態にある場合、これらのケースをどのように探すのですか? この場合、「レコード待機スレッド」の設定が役立つ場合があります。 次に、測定時に、プロセッサが使用されていない状態が記録されます。 経過時間ではなく、関数ごとの測定数の列が下部のテーブルで自動的にオンになります。 これらの列の表示はカスタマイズできますが、デフォルトでは時間または測定数が表示されます。



この例を考えてみましょう:



- (void)someMethod { [self performSelector:@selector(nothing:) onThread:[self backThread] withObject:nil waitUntilDone:YES]; } - (void)nothing:(id)object { for (int i=0; i<10000000; ++i) { [NSString stringWithFormat:@"get%@", @"Some"]; } }
      
      





このようなコードの起動でアプリケーションを測定すると、次のような結果が得られます。







この図は、やがてメインストリームが94ミリ秒と2.3%を要し、測定値(サンプル)が9276と27%を要したことを示しています。 ただし、違いは必ずしもそれほど顕著ではありません。 実際のアプリケーションでそのようなケースを探す方法は? ここでは、ストリーム形式のグラフ表示モードが役立ちます。







このモードでは、スレッドがいつ開始し、いつアクションを実行し、「スリープ」するかを確認できます。 上部のグラフを表示するだけでなく、下部のテーブルにサンプルリストの表示を有効にすることもできます。 メインスレッドの「スリープ」の領域を見ると、インターフェイスの中断の原因がわかります。



システムコールにこだわらないでください

測定を実行すると、システムコールが非常に簡単に実行されます。 システムコードを処理するのにかかるすべての時間が判明しました。 どうすればいいですか? 実際、主なことはこれにこだわることではありません。 機会がある限り、これらの課題を掘り下げる必要があります。 掘り下げてみると、コールバックやコードを呼び出すデリゲートになることが簡単に判明する可能性がありますが、時間の顕著な浪費はまさにそのためです。



切断する

だから、容疑者が見つかりました。 やり直す前に、アプリケーションがそれなしでどのように動作するかを確認する必要があります。



阻害の潜在的な犯人が多数いる可能性があり、プロファイラーでこれらすべてを測定するのは問題です。 たとえば、問題は一部のユーザーのみによって適切に再現され、常にではなく特定の状況でのみ再現されます。 正しい方向に進んでいるかどうか、場所を測定して最適化しているかどうかをすばやく理解するには、これらのモジュールを無効にすることが非常に役立ちます。



すべてが既にオフになっていて、理想からかけ離れている場合は、反対側に移動する必要があります。 空のアプリケーションを作成し、その機能を増やします。



デバイスの技術的特徴を考慮に入れる

製品の仕様は変化しており、これも検討する価値があります。 たとえば、iPhone 4Sからはマルチコアプロセッサの使用が開始されました。 したがって、複数のコアを使用するため、マルチスレッドの使用はより効率的です。 ただし、シングルコアプロセッサでは、コアを1つしか使用できないため、最終結果が遅くなる可能性がありますが、同時にスレッドのコンテキストの切り替えに追加のリソースを費やします。



大きなフレームワークを接続するときは注意してください

接続するメカニズムがますます強力になるほど、引き継がれます。 あなたが状況を制御することが少ないほど。 そして、それに応じて、アプリケーションの柔軟性が低下します。 私たちのケースでは、CoreDataにしっかりと座っていました。 素晴らしいテクノロジー。 マイグレーション、FetchResultController、キャッシングのあらゆる種類のサポートは非​​常に魅力的です。 しかし、アプリケーションの起動を取ります。 CoreDataスタックを初期化するには、少なくともデータベースとモデルをロードする必要があります。 CoreDataなしでsqliteを使用する場合-モデルをロードする必要はありません。 この例では、モデルには26個のエンティティが含まれています。 特に古いデバイスでは、起動速度が最も急激に感じられるため、その読み込みには明確な時間がかかります。



私たちのアプリケーションは活発に開発されているため、データベースにエンティティを追加する必要が常にあります。 便利な移行メカニズムのおかげで、これは問題を引き起こしません。 しかし、現在、それらはほぼ40個あり、まず、これはアプリケーションのサイズに大きな影響を与えます。 合計すると、すべての移行で約30%増加します。 さらに、移行は順番に機能します。 そのため、存在するほど移行が長くなります。 そして、これは再び起動速度に影響します。



また、削除の問題も発生しました。 このモデルと十分に大きなベースを使用すると、すべてのエンティティに影響する削除には約10分かかりました。 CoreData SQLDebugの魔法のデバッグオプションをオンにすると、膨大な数のSELECT、UPDATE、および少しのDELETEが見つかりました。 ここでの主な問題は、NSManagedObjectContextにdeleteObjectsメソッドがないことです。 つまり、オブジェクトは一度に1つずつしか削除できませんが、SQL自体はDELETE ... WHERE someValue IN ...で削除できます。また、各オブジェクトを削除するには、そのキーを選択してから削除するだけです。 同様に、依存オブジェクトの削除が発生します。



私たちの状況では、モバイルデバイスユーザーは原則としてそのような長い時間を待たずにアプリケーションを「殺す」という事実によって、状況はさらに悪化します。 結果は壊れたベースです。



結論

ご覧のとおり、モバイルアプリケーションの速度を最適化する方法は多数あります。 しかし、数字とグラフに囲まれているため、現実から脱却する必要はありません。 戦闘状態で最適化の効果を感じることができるように、アプリケーションを動作可能な状態に保つことをお勧めします。 残念ながら、多くの場合、開発者は最適化に十分な注意を払っていないか、このタスクにあまりにも熱心です。 主なことは、最適化によってユーザーに具体的な結果がもたらされることを忘れないことです。 効果がホメオパシーで得られる最適化自体のための最適化は、時間と労力の無駄です。 すべてが適度なはずです。



All Articles