モバイルゲームの開発者は、最新のハイエンド プレミアム スマートフォンから量販品や旧式の製品までの幅広いデバイスで、コンテンツがうまく動作するように尽力しています。モバイルゲームのコンテンツが複雑化するにつれて、開発者は、フレームレートを安定させる一方で電力消費を抑えるために必要な有益な情報を提供してくれる、良質のツールに頼るようになっています。
本記事では、新しい Arm Mobile Studio のツールのコレクションが、Android のパフォーマンス分析にどのように役立つか、そしてゲームエンジンとどのように連携して、より説得力のあるパフォーマンス分析能力を生み出すことができるかについて説明します。Arm Mobile Studio の Starter Edition は無償で利用でき、このブログで使用しているサンプルプロジェクトのソースコードは github で入手できます。
このブログでは、Arm Mobile Studio の中で最も汎用的かつ最も詳細なパフォーマンス分析コンポーネントである Streamline に注目することにします。モバイルデバイス上の Arm Cortex-A CPU や Arm Mali GPU のリソースがゲームのさまざまな局面でどう利用されているかを実際に確認するために、Streamline と Unity をどのように統合するかについて説明します。
Streamline について
Streamline は、Android デバイス上のいくつかの情報源からサンプルベースおよびイベントベースのパフォーマンス データを収集して、その集計結果をさまざまなビューに表示します。それらのビューのうち、このブログではタイムライン ビューに注目しています。画面の上半分には収集したシステム パフォーマンス カウンタが表示され、下半分にはさまざまな種類の情報が同じタイムラインで表示されます。次の図は、演算アクティビティーがプロファイリング対象アプリケーションのスレッド間でどのように分散されているかを表すヒートマップを示しています。
分析対象
ここでは、非常にシンプルな Unity コンテンツである、手続き型で生成されたテレインのフライスルーを分析します。カメラは曲がりくねりながら進み、必要に応じてオンザフライで新しいテレインタイルが生成されます。カメラから離れすぎたタイルは削除されるため、いったんシーンが満たされたあとは、時間が経過してもシーンの複雑さはおおむね同じ状態のままです。カメラがゆっくり移動するときは新しいテレイン生成の速度は非常にゆっくりであり、カメラの移動速度が上がったときはテレイン生成の速度も上がる必要があります。各タイルのエッジは暗めの色でレンダリングされているため、各タイルのサイズを確認できます。
テレインタイルの生成は多くの演算を必要とするため、Unity のメインスレッドの実行を妨げないバックグラウンド スレッドをディスパッチできる Unity Job Scheduler を使用します。そうすることで、新しいテレインが生成されるたびにぎくしゃくした動きになることはなく、フレームレートが安定します。
このデモは、4 つの異なるシーンを通過するように設定されています。各シーンはまったく同じように見えますが、テレインタイルの生成方法が異なっています。次の図に示しているように、このテレインは多数のテレインブロックで構成されていて、各ブロックは固定サイズです。各ブロックはいくつかのメッシュ (緑色のテレイン用に 1 つ、黄色のテレイン用に 1 つ、水面用に 1 つ) で構成されていて、各メッシュは固定解像度です。プレイヤーの周囲に生成されるタイルの数は、レンダリング距離によって制御されています。
4 つのシーンは次のように設定されています。
シーン | レンダリング距離 | テレインタイルのサイズ | テレインの解像度 | テレインの並列生成数 |
---|---|---|---|---|
1 | 3 | 20×20 | 32×32 | 8 |
2 | 3 | 20×20 | 32×32 | 1 |
3 | 6 | 10×10 | 16×16 | 8 |
4 | 6 | 10×10 | 16×16 | 1 |
2017 年にリリースされた Huawei P10 スマートフォンでプロファイリング アクティビティーを実行します。このスマートフォンには、4 基の高性能な Arm Cortex-A73 CPU コア、4 基の高効率の Arm Cortex-A53 CPU コア、および Arm Mali-G71 MP8 GPU で構成されている HiSilicon Kirin 960 チップが搭載されています。
Unity でのプロファイリング
Unity 自体にはプロファイラーが含まれていて、Android デバイスでは非常に役立ちます。
Unity のプロファイラーでは、ジョブがいつスケジュールされているかは明確に示されますが、プラットフォームの物理リソース (このブログの目的である CPU や GPU) や、それらがどのように使用されているかは示されません。60FPS に到達することもあるようですが、すべての CPU コアが限界まで使われているでしょうか、そしてそのためにバッテリが消耗されているのでしょうか?そこで Streamline の出番です。このブログの残りの部分では、Streamline がどのようなデータをキャプチャおよび提示するか、Streamline のアノテーション機能をどのように使用して、Unity ゲームからの高水準のコンテキストを Streamline に渡してデータを解釈しやすくできるかについて説明します。
Streamline による Unity のプロファイリング – その機能
Unity ゲームにアノテーションをどのように挿入できるかについて説明する前に、Streamline の3つのアノテーション機能を利用するようにゲームを変更した場合、サンプルコンテンツの最終的な結果がどのように見えるかを確認しておきましょう。
- マーカーは最もシンプルな形式のアノテーションであり、Streamline のタイムラインビューに表示される一時点に対するラベルです。
- チャネルは、より構造的で、各スレッドに沿った行ごとに情報を提供します。アノテーションをチャネル内に配置でき、マーカーとは違って、各アノテーションは一定時間に及びます。
- カスタム アクティビティー マップは最も高度な形式のアノテーションであり、複雑な依存関係が存在することがあるグローバルな (スレッド間の) アクティビティーを示すためのメカニズムです。各カスタム アクティビティー マップは、Streamline の UI の下半分に独自のビューとして表示されます。
1 つのプロファイルが収集されると (その後もプロファイルの収集は続行される)、Streamline に表示されるので、何が起きているのかを把握し始めることができます。
コンテンツを調べるときに、まず目を引くのはマーカー (タイムラインの上部に緑色で表示) であり、この例では各フレームが始まっている位置を表しています。
フレームレートが規則的ではなく、すべての CPU コアにわたって相当量の集中的なアクティビティーが見られます。フレームレートがゆっくり始まってから、上昇し、ときどき休止しています。それはテレイン生成から予期されることとかなり一致していますが、さらに深く調べることはできるでしょうか?
Streamline のタイムラインビューは 2 つに分けられていて、上部にはメトリックグラフが表示され、下半分にはヒートマップなどさまざまなものが表示されます。ヒートマップには、システム全体で作業がどのように分散されているかが示され、特定のプロセスやスレッドに起因する作業のみが表示されるように上部のタイムラインをフィルタできます。ヒートマップを調べて、まず UnityMain スレッドを選択し、その後にすべてのワーカースレッドを選択することによって、Unity のメインスレッドとジョブスケジューラ内のスレッド間で CPU アクティビティーがどのように分割されているかを確認できます。
UnityMain スレッドの CPU プロファイル。Cortex-A73 CPU の集中的なアクティビティーが大きいことを示している。 | すべてのワーカースレッドの CPU プロファイル。Cortex-A73 と Cortex-A53 CPU の両方にまたがる集中的なアクティビティは小さいことを示している。 |
メインスレッドを見ていきましょう。左側のスクリーンショットをよく見ると、注目している UnityMain スレッドの横に「A」マーカーがあります。これは、Streamline のアノテーションチャネルが存在することを意味しています。タイムラインを少し拡大し、UnityMain を展開して、何が起きているのかを確認します。
Scene 行と TerrainController 行は、ゲームに配置されているアノテーションによって生成された、Streamline のチャネルです。Scene チャネルには現在どのシーンが実行されているかが示されていて、テレインタイルのサイズが 20×20、テレインの解像度が 32×32、レンダリング距離が 3、8 スレッドを並列に実行可能であることがわかります。
TerrainController チャネルは、コード内の注目すべき特定のピースが Unity のメインスレッドでいつ実行されているかを表すために使用されています。青色のブロックは、Terrain ジョブが完了するときに実行されていたコードを示しています。緑色のブロックは、新しい Terrain ジョブが生成のためにスケジュールされている位置を示しています。メインスレッドのすべてのアクティビティーは、基本的には、ジョブが完了するときに行われる必要がある作業と、最終的なメッシュが生成されてシーンに挿入されるのに必要な作業のためであることがわかります。
特定のスレッドに注目するだけでなく、特定の期間に限定して分析することもできます。Streamline の calipers (キャリパ) を使用すると、特定の時間範囲を分析用にマークできます。この例では、Terrain ジョブの完了に関連するアクティビティーの集中期間の開始と終了を選択しています (キャリパはタイムラインビューの上部で設定されています)。
ここでコールパスビューに切り替えると、キャリパで選択している時間範囲内のどこで時間がかかっているかを正しく把握できます。Unity 用の IL2CPP スクリプティング バックエンドを使用しているため、デフォルトの Mono ランタイムを使用している場合よりずっと多くの情報が取得されています。ここでは何が起きているのかについて徹底的に詳しく調べることはしませんが、もっと深く掘り下げるに値するさまざまなことが起こっているのは明らかです。
ワーカースレッドについて確認する
ワーカースレッドのみが表示されるようにフィルタした場合、最大の 8 つのジョブが並列に実行されるように要求したとすれば、意外なことは何もありません。次のスクリーンショットでは、Cortex-A53 のクラスターを展開しているので、個々のコアの使用状況がわかります。
新しい Terrain ジョブがスケジュールされていることを表す TerrainController チャネルのいくつかの緑色のブロックがあり、すべてのコアにわたってある程度集中的なアクティビティーがあり、Terrain ジョブが生成されたときにそのジョブをメインスレッドで処理するための TerrainController での青色のアクティビティーがあります (UnityMain を選択していないため、このグラフにはメインスレッドのアクティビティーは表示されていません)。
これを2番目のシーンのアクティビティーと比較するのは興味深いものがあります。2番目のシーンでは、テレインタイルの複雑さは同じですが、一度に 1 つのテレインタイルのみがスケジュールされるようにしています。
ここでは以下のことに注意してください。
- CPU アクティビティーの集中はかなり減っていて、大部分のコアはほとんどの時間でアイドル状態またはそれに近い状態です。
- フレームレートは完璧ではありませんが、とても滑らかです。一度に完了するフレームは 1 つだけなので、メインスレッドでのアクティビティーの大きな集中は減っています。
このプロファイルを、より小さいタイルを使用している 3 番目のシーンと比較することもできます。
ご覧のように、CPU アクティビティーの集中は大幅に少なくなっていて、メインスレッドでの青色の作業完了のブロックは大幅に短縮されています。そのため、1 番目のシーンに比べてフレームレートがより滑らかになっています (ただし、当然ながら全体的なジョブ数は増えているので、テレイン生成が、カメラがテレインを飛び越える速度に遅れないようにしておく必要があります)。
小さいタイルサイズで一度に 1 つのみのテレイン生成ジョブが実行されている 4 番目のシーンでは、全体的なフレームレートは最も滑らかですが、テレイン生成がカメラに遅れないような十分な速度で行われるように細心の注意を払う必要があります。元のビデオでわかるように、カメラが 4 番目のシーン上で高速に移動するときに常にそうなっているとは限りません。
最後に、カスタム アクティビティ マップを使用して、ワーカースレッドがどのようにテレイン生成を行っているかについての有益な情報をさらに多く取得します。各カスタム アクティビティー マップは、これまでヒートマップを表示するために使用していた、左下にあるメニューで選択肢として表示されます。
[Terrain Generation] ビューを選択すると、テレイン生成の各アクティビティーに対して色付きのボックスが表示されて、その開始時間と終了時間が表示されます。マウスポインタを置くと、そのテレインタイルのワールド座標、開始時間、および完了までの使用時間が表示されます。このスクリーンショットでは、Mali GPU で行われた演算動作もグラフ化しています。予想どおりですが、テレインが満たされるにつれて GPU アクティビティーは着実に増加しています。このスクリーンショットは、1 番目のシーンの始めに注目しているときに、最大で 8 つの大きいタイルを同時に生成している ところを撮ったものです。メインスレッドですべての新しいジオメトリが準備されている間の休止によって、GPU は長時間アイドル状態になっています。
より小さいサイズのタイルを順次生成している 4 番目のシーンに移動すると、GPU アクティビティーの傾斜がずっと滑らかになっていることがわかり、Terrain ジョブが一度に 1 つだけ実行されていたことをはっきり確認できます (また、タイルサイズが小さいため、各ジョブの所要時間が短くなっています)。
ここまで、より概要レベルのコンテキストが提供されるようにゲームそのものでアノテーションを使用した場合に、Streamline で得られるいくつかの付加的な有益情報について段階的に説明してきました。以下のアノテーションを使用しました。
- 新しいフレームが開始されたことを示すためのマーカー。
- どのシーンが実行中であり、メインスレッドでどのアクティビティーが実行されていたかを示すためのチャネル。
- 非同期でスケジュールされたテレイン生成ジョブの挙動を示すためのカスタムアクティビティーマップ。
どれもすばらしいものですが、その仕組みはどうなっているのでしょうか?
Streamline のアノテーションについて
Streamline の仕組みについてさらに詳しく見ていきましょう。Android アプリケーションを分析するときに、そのデバイス上で gator という別のプロセス (同じユーザーがアプリケーションとして実行している) が実行されていて、さまざまなハードウェアソース (Mali GPU や Arm Cortex-A CPU など) からプロファイリング情報を収集し、メトリックを集計したストリームをユーザーのコンピューターに送信します。Streamline のアノテーションは、アプリケーション自体が独自のマーカーやメトリックをそのストリームに挿入できるようにするためのメカニズムです。
Unity で Streamline のアノテーションを使用する方法
Streamline のアノテーションでは特有のプロトコルが使用されていて、オープンソースの C 言語での実装が Arm Mobile Studio の一部として提供されています。Streamline のアノテーションを Unity コンテンツ内から簡単に生成できるように、C 言語の実装に対するC#のラッパーをいくつかユーザーが準備する必要があります。このウォークスルーで使用するラッパーおよび C 言語での実装は Unity アセットパッケージとして入手できます。そのパッケージをダウンロードし、自分のプロジェクトにカスタム アセット パッケージとしてインポートします。そのパッケージでは Arm 名前空間に新しいメソッドが追加されていて、そのメソッドを使用するとユーザーのプロジェクトで Streamline のアノテーションを簡単に使用できます。API のドキュメントは、そのパッケージに含まれている README.md ファイルに記載されています。
最適なエクスペリエンスのための Unity プロジェクトのセットアップ
最も高速かつ最も簡単に Android ビルドの分析を Unity から取得する場合は、Android Playe rの特定の設定を以下のようにしておく必要があります。
スクリプティング バックエンドとして IL2CPP を使用していることと、 [C++ Compiler Configuration] が [Debug] に設定されていることを確認します。そうすることによって、スクリプトがネイティブコードにコンパイルされてパフォーマンスが向上するだけでなく、Streamline でデバッグ情報を表示して、コールパスビューでパフォーマンスデータをユーザーの関数に戻してマッピングできることも意味します。 |
ターゲットアーキテクチャを「ARM64」に設定します (デフォルトでは「ARMv7」)。最近のほとんどのモバイルデバイスは 64 ビットであり、結果的にコード生成の品質が向上します。
マーカーの追加
マーカーは最も簡単にできるアノテーションです。提供されているメソッドは文字列およびオプションとして色を受け取ります。たとえば、フレームごとのマーカーを緑色で出力するには、Game Objects のいずれかで次のコードが使用されます (Unity アーキテクチャに慣れていないユーザーのために説明しておくと、Update() メソッドはフレームごとに 1 回自動的に呼び出されます)。
void Update () { Arm.Annotations.marker("Frame " + Time.frameCount, Color.green); }
チャネルの追加
チャネルを使用するのは大して難しくありません。まず、名前を指定してチャネルを作成します。 そうすると、Channel オブジェクトのメソッドを使用してそのチャネルにアノテーションを送り込むことができます。次にサンプルを示します。
channel = new Arm.Annotations.Channel("Scene"); channel.annotate(sceneDescription, color);
チャネル内のアノテーションは一定時間に及ぶことを忘れないでください。次のアノテーションを開始する前にアノテーションを終了する場合は、end() メソッドを使用できます。たとえば、メインスレッドで Terrain ジョブの完了を行う TerrainController の一部は、次のようにラップされています。
// Begin annotation channel.annotate("Completing", Color.blue); Mesh mesh = obj.GetComponent().mesh; mesh.vertices = job.vertices.ToArray(); mesh.uv = job.uv.ToArray(); mesh.uv2 = job.uv2.ToArray(); mesh.SetTriangles(job.grassTriangles.ToArray(), 0); mesh.SetTriangles(job.sandTriangles.ToArray(), 1); mesh.SetTriangles(job.waterTriangles.ToArray(), 2); mesh.RecalculateNormals(); // End annotation channel.end();
カスタム アクティビティー マップの使用
CAM (カスタム アクティビティー マップ) は、チャネルの上位にある単なるもうひとつのレイヤーと考えることができます。まず CAM に名前を付けてから、その中にトラックを作成します。そうすると、チャネルにアノテーションを追加したのとほとんど同じようにして、そのトラックにアノテーションを追加できます。
このサンプルでは、テレイン生成 CAM は次のように作成されています。
terrainCAM = new Arm.Annotations.CustomActivityMap("Terrain Generation"); terrainTracks = new Arm.Annotations.CustomActivityMap.Track[16]; for (int i = 0; i < 16; i++) { terrainTracks[i] = terrainCAM.createTrack("TerrainJob " + i); }
ただし、この適用例には厄介な問題が1つあります。Unity Job System でジョブが実行中である場合、そのジョブはゲームの他のオブジェクトモデルとほとんどやり取りできません (これはスレッドセーフに保つのに役立ちます)。そのジョブ内でできることは、開始時刻と終了時刻を覚えておくことだけであり、ジョブのアクティビティーを CAM に登録できるのは、メインスレッドでそのジョブが除去されるときです。
C# のラッパーでは、Streamline のアノテーションで必要とされる形式で現在時間を返す関数が提供されていて、その関数をジョブ内から安全に呼び出すことができます。
UInt64 startTime = Arm.Annotations.getTime();
メインスレッドに戻ったときに、使用するトラック (オーバーラップしないようにトラックをプールで管理します。その方が視覚的に便利です) を選択し、ジョブをそのトラックに登録します。ここで、job.timings は、ジョブの開始時刻と終了時刻が入っている 2 要素の配列です。
track.registerJob(obj.name, Color.grey, job.timings[0], job.timings[1]);
これで終了です。この Unity パッケージを徐々に改良して、もっと多くの機能を追加していこうと考えています。みなさまからのフィードバックをお待ちしています。
Streamline での基本的なプロファイルの収集
Streamline で最初にプロファイルを収集する際には、いくつかの手順を行う必要がありますが、一度セットアップすれば、あとは非常に単純明快です。
まず、Windows、Mac、または Linux の無償版の Arm Mobile StudioのStarter Edition をダウンロードしてインストールします。
前述の Streamline のアーキテクチャで説明したように、下記の手順を、ユーザーが実施しておく必要があります。
- モバイルデバイス上で gator を実行し、gator がアプリケーションにアクセスできるようにしておく必要があります。
- デバイスからデータを取得して gator に渡す手段が必要です。
Streamline では、それを行うための手段が提供されていますが、広範なデバイスにわたる堅実で最もシンプルな方法は、最初に下記の主要な情報を確実に把握しておくことです。
- アプリケーションが 32 ビットと 64 ビットのどちらであるか。 前述の手順に従って、ARM64 オプションを指定して Unity ゲームをビルドしていれば、64 ビットです。
- アプリケーションのパッケージ名 (Unity の Android Player 設定で指定した名前)。ここでのサンプルアプリケーションの場合、この名前は com.Arm.InfiniteTerrain です。
- gator のバイナリを入手する方法。Arm Mobile Studio のインストール先の streamline/bin/arm フォルダ (32ビット) または streamline/bin/arm64 フォルダ (64ビット) にあります。
これらの情報を把握したら、以下の手順に従って分析を実行します。
- アプリケーションがデバイスにインストールされていることを確認します。
- Android の adb ツールを使用して、モバイルデバイスから、Arm Mobile Studio を実行しているシステム上のローカルポートにネットワークポートを転送します。
- adb を使用して gator (ユーザーのアプリケーションに応じて 32 ビット版または64 ビット版のいずれか) をデバイスにプッシュし、アプリケーションと同じパッケージ名を使用して gator の実行を開始します。
- Streamline を起動し、トラフィックの転送先として adb で使用したローカルポートに接続します。この時点で、どのパフォーマンスカウンタを収集対象にするかを選択できます。
- モバイルデバイス上でアプリケーションを起動します。Streamline は分析データを受信すると、その収集を開始します。
gator が実行中であれば、新しいバージョンのアプリケーションをインストールし、Streamline を起動および停止して、gator を再起動することなくさらに分析を実行できます。
このプロセスを簡単にするために、gator と adb を設定して実行するために使用できる gatorme スクリプトをダウンロードできます。このスクリプトで指定する必要があるのは、実行する gator バイナリのパス、アプリケーションのパッケージ名、およびどの Mali GPU がデバイスに搭載されているか (gator がデバイスを探って、どの GPU が搭載されているかを割り出すことができない場合に役立ちます) だけです。このスクリプトでは、このスクリプトが幅広いモバイルデバイス上で確実にうまく動作するようにし、ユーザーがプロファイリングアクティビティーを終了したときに gator が適切にシャットダウンされるようにするための、いくつかの手順も実行されます (いずれ近いうちにgatorme の機能を Streamline に直接取り入れる予定です)。
詳細は gatorme のドキュメントに説明されていますが、ここでは作業サンプルとして、デバイスにこの APK をインストールすると、InfiniteTerrain のコンテンツをどのようにプロファイリングできるかを示します。
まず、コマンドラインで gatorme を次のように実行します。
$ ./gatorme.sh com.Arm.InfiniteTerrain G71 ./mobilestudio-macosx/streamline/bin/arm64/gatord
これで、Streamline を起動してキャプチャする準備が整いました。設定が下図のように正しく行われていることを確認します。
セットアップできたら、開発、分析、修正の手順を繰り返すのは簡単です。アプリケーションをシャットダウンして gatorme スクリプトは実行したままにしておいて、Unity で [Build And Run] を指示して Streamline の情報をさらにキャプチャすることができます。
このブログが興味深いもので役立つものと思っていただければ幸いです。InfiniteTerrain のサンプルのすべてのソースコードは GitHub (Apache 2.0 ライセンス) で入手していただけます。ソースコードとグラフィックスアセットだけでなく ArmMobileStudio.unitypackage もあります。この Unity のカスタムアセットパッケージを使用すると、ユーザーは自分のプロジェクトにインポートしてプロジェクトに Streamline のアノテーションを追加できます。ビルド済みの InfiniteTerrain.apk もあります。この APK は 64 ビット版の Android 開発用ビルドであり、一部のコンテンツの分析をすばやく始めるだけであればすぐにデバイスにデプロイできます。
最後になりましたが、Arm Mobile Studio やグラフィックスやゲーム全般での Arm に関して不明な点がありましたら、グラフィックスとマルチメディアのフォーラムに参加いただくか、または下記の Arm Mobile Studio の開発者向けサイトでツールに関する詳細情報を参照してください。
この記事は、Arm 社の Software Tools Blog に公開されている「Better Together: Integrating Arm Mobile Studio with Unity」の日本語訳です。