Profilerにある「嘘つき」について話しましょう

WaitForTargetFPS、Gfx.WaitForPresent と Graphics.PresentAndSyncは私たちがよく聞かれたパラメーターです。おそらく、この記事を読んでいるあなたも、ProfilerでこれらのパラメーターのCPUコストが大きすぎる状況に遭うことがありましたでしょう。これについて、今日、これらのパラメーターの特定の意味と発生規則について説明しましょう。

---
##WaitForTargetFPS
通常、このパラメーターは**CPUコストが低すぎ、そして設定されたターゲットフレームが通過された場合に（Application.targetFrameRate）出現します**。前のフレームの時間コストがターゲットフレームより低い時、当該フレームで１つのWaitForTargetFPS待機時間を生成して、ターゲットフレームレートを維持します。

**解析：**
実際、このアイテムはUnityエンジンのメインループに最も早く実行します。つまり、エンジンは、前のフレームのCPU時間コストに従って、現在フレームにWaitForTargetFPSを追加することにより、実行中のFPSをターゲット値に維持します。たとえば、ターゲットフレームレートが30フレーム/秒で、前のフレームに15msかかった場合、現在フレームのWaitForTargetFPSは18（33〜15）msになりますが、当該フレームにある他の時間コストが28msの場合、Profilerに当フレームの合計時間コストが46（18+28）msになります。

ですから、この値がProfilerのコストが比較的に高い状況を引き起こします。実際には時間コストの「錯覚」です。最適化する過程にこれを無視することができます。


##Gfx.WaitForPresent && Graphics.PresentAndSync
通常、Profilerにこの2つパラメーターのCPU使用率が高く、リリースバージョンでのみ表示されます。この原因は、CPUとGPU間の垂直同期（VSync）が引き起こします。**主にプロジェクトがマルチスレッドレンダリングをオンにするかどうかに関します。**マルチスレッドレンダリングがオンにする場合はGfx.WaitForPresentですが、オンにしていない場合にはGraphics.PresentAndSyncであります。

Graphics.PresentAndSyncは、メインスレッドがPresentを実行するときの待機時間と垂直同期の待機時間を指します。Gfx.WaitForPresentの名前から見ると、同じにPresentを実行するときの待機時間ですが、実際には沢山の内容を省略しています。**本当の意味は、レンダリングサブスレッド（Rendering Thread）でPresentを実行するために、現在のメインスレッド（MainThread）が待つ必要のある時間です。**それでも厄介に聞こえますが、以下で詳しく説明しましょう。

プロジェクトがマルチスレッドレンダリングを開くと、エンジンはPresentなどの相関作業を出来るかぎりにレンダリングスレッドに配置して執行します。つまり、メインスレッドは指令を介してレンダリングスレッドをコールし、Presentを実行させることだけで、メインスレッドの圧力を減らします。しかし、CPUがPresent操作を実行したい場合、GPUが前回のレンダリングを完了するのを待つ必要があります。GPUのレンダリングコストが高い場合、CPUのPresent操作は常に待機操作になります。この待機時間は、つまり当該フレームのGfx.WaitForPresent時間であります。下図のようです。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/9d171fcf-9654-827d-0c43-9a506e06ed7e.png)
同様に、プロジェクトがマルチスレッドレンダリングを開かない場合、エンジンはメインスレッドでPresent（現在のほとんどのモバイルゲームはこの方法を採用しています）を実行します。当然、Present操作はまだGPUが前回のレンダリング操作を完了するまでに待つ必要があります。GPUのレンダリングコストが大きいなら、CPUのPresent操作はいつも待機しています。この待機時間は、つまり当該フレームのGraphics.PresentAndSync時間です。下図のように。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/054a0b5c-99eb-357b-aa65-2936f060d353.png)
この状況を示すために、より極端な例を作成しました。Unity 5.3.3バージョンでは、60個のフルスクリーンUIPanelが作成され、マルチスレッドレンダリングがそれぞれオンとオフになり、TargetFPSを設置しません。では、SAMSUNG S6デバイスで、このパラメーターのCPUコストは下記のようです。

**マルチスレッドレンダリングON：**
![3.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/90bdc7f0-d3b8-48aa-f4f8-fb33989f6b73.jpeg)

**マルチスレッドレンダリングOFF：**
![4.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/d213eb06-933b-a6b9-1bf7-06bef56596e7.jpeg)

したがって、Gfx.WaitForPresentまたはGraphics.PresentAndSyncのCPU時間コストがプロジェクトで非常に時間がかかる場合、**それらが何か妙な操作を行ったためではなく、現在のレンダリングタスクが重すぎで、GPUの負荷が高すぎるためです。**

同時に、垂直同期を開いたプロジェクトに対して、Gfx.WaitForPresentとGraphics.PresentAndSyncもCPU占用が高い可能性があります。この問題を説明する前に、例として「電車を乗る」を取り上げましょう。一般的に、電車が各駅に到着する時間は固定で、10分ごとに乗客を一組乗らせてと仮定します。しかし、あの1秒に到着するばかりの乗客はほとんどいないので、2分早く到着すれば、電車に乗るのに2分待つだけでいいのですが、乗り遅れれば、1分でも乗れません。次の電車が来るまで9分待たなければいけません。

私たちが上記の状況をよく遭います。GPUのレンダリングパイプラインでのfront bufferとback bufferを変換する動作原理は、実際に「電車を乗る」と同じです。GPUGPUパイプラインを電車として簡単に想像することができます。モバイルデバイスに対して、GPUのフレームレートは一般的に30fpsまたは60fpsであり、つまりVSyncは33msまたは16.6msごとに一回到着します。CPUのPresentは電車に乗る乗客で、一人一人で違う目的地に乗って行きます。乗客の早い到着と遅い到着と同様に、CPUのPresentにも同じような状況があります。例えば、
>CPU消費が非常に小さいで、Presentは非常に早く実行されますが、この時点でVSyncはまだ到着していませんから、待機時間が長くなり、つまりGfx.WaitForPresentとGraphics.PresentAndSyncのCPUコストは非常に高く見えます。下図はUnity5.3.3バージュンで、1つの空きシーン（マルチスレッドレンダリングOFF、TargetFPSを設定していない）がSAMSUNG S6でのGraphics.PresentAndSyncのCPU占用状況です。

![5.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/f8b08c66-9196-2ab6-cb09-b43f454a74f9.jpeg)

>CPUコストが高いため、Presentが執行中にVSync操作を見逃させ、これで、Presentは次のVSyncが到着するまで待つ必要があり、Gfx.WaitForPresent と Graphics.PresentAndSyncのCPUコストを高くなさせます。この状況はCPUで過剰なアセットをロードする時によく発生します。たとえば、WWWが大きいAssetBundleをロードする時やResource.Loadが大量なTextureをロードする時など。

上記の説明を通じて、現在のあなたはもうGfx.WaitForPresent と Graphics.PresentAndSyncに対して深く理解していると願っています。この2つのパラメーターが、占めるCPUの占用量に関係なく、パラメーター自身の問題ではありません、プロジェクトの他の部分が引き起こす問題です。この点について、印象をさらに深められるようにまとめます。

>1、これら2つのパラメーターのCPU使用率が高い主な原因は3つあります。
CPUコストが低いため、CPUはGPUがレンダリング作業を完了するのを待つか、VSyncの到着を待ちます。
2、CPUコストが非常に高いため、Presentを現在フレームのVSyncを見逃させ、つまり次のVSyncが到着するのを待つ必要があります。
3、CPUコストが非常に高いため、CPUのPresentがGPUの前のフレームレンダリングの完了を待つ必要があります。

最後に、これらのパラメーターのCPU占用をどうすれば最適化して減らせますか？これは、**Gfx.WaitForPresentとGraphics.PresentAndSyncの2つのパラメーターを無視し、君が最適化できる他のすべてを最適化することです。**

---
UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、**パフォーマンス分析**と**最適化ソリューション**及び**コンサルティングサービス**を提供している会社でございます。

今なら、UWA GOTローカルツールが**15日間に無償試用**できます！！
よければ、ぜひ！

UWA公式サイト：https://jp.uwa4d.com
UWA GOT OnlineレポートDemo：https://jp.uwa4d.com/u/got/demo.html
UWA公式ブログ：https://blog.jp.uwa4d.com