Search Unity

Async Upload Pipeline(AUP)でローディングのパフォーマンスを最適化する

, 10月 8, 2018

ローディング画面が好きという人は誰もいないでしょう。ロード時間は Async Upload Pipeline(AUP)パラメーターを簡単に調節するだけで大幅に改善できることをご存知でしたか?本記事では、AUP によるメッシュとテクスチャをローディングする仕組みを詳しくご紹介します。ロード時間を大幅に短縮する上で、この仕組みを理解しておくと役に立つと思います。これによって実際にパフォーマンスが 2 倍改善されたプロジェクトもあります。

AUP が機能する技術的な仕組みや、その効果を最大限に引き出すために使う API を知りたい方は、ぜひ以下を読み進めてください。

AUP をお試しください

最適化された最新版の AUP の実装は Unity 2018.3 ベータ版でご使用いただけます。

 

Unity 2018.3 ベータ版をダウンロード

 

まず最初に、AUP がどんな場合に使用されるか、およびローディング処理のプロセスについて、詳しくご紹介します。

AUP はどんな場合に使用されるか

バージョン 2018.3 以前は AUP はテクスチャのみを扱っていました。2018.3 ベータ版からはテクスチャとメッシュがロード可能になりましたが、一部例外もあります。読み込み/書き込み有効のテクスチャとメッシュ、および Compressed(圧縮)メッシュには AUP は使用できません。([注]バージョン 2018.2 で追加された Texture Mipmap Streaming も AUP を使用します。)

ローディング処理の流れ

ビルド処理中、テクスチャやメッシュオブジェクトはシリアライズされたファイルに書き込まれ、大きなバイナリデータ(テクスチャあるいは頂点データ)は付随の .resS ファイルに書き込まれます。このレイアウトはプレイヤーデータとアセットバンドルの両方に当てはまります。オブジェクトとバイナリデータを分けることにより(通常、小さなオブジェクトが入る)シリアライズされたファイルのより高速な読み込みが可能になり、大きなバイナリデータは .resS から効率的に読み込めるようになります。テクスチャオブジェクトやメッシュオブジェクトは、デシリアライズされた時に AUP のコマンドキューへコマンドをサブミットします。コマンドが完了した時点で、そのテクスチャあるいはメッシュデータは GPU へのアップロードされており、オブジェクトがメインスレッドに統合可能になっています。

図表:ビルド用にシリアライズされたメッシュおよびテクスチャデータのレイアウト

アップロード処理中、.resS ファイルからの大きなバイナリデータは固定サイズのリングバッファに読み込まれます。一旦メモリに入ったデータはレンダースレッド上でタイムスライス方式で GPU にアップロードされます。システムの挙動を変えるために変更できる 2 つのパラメーターは、リングバッファと、タイムスライスの長さです。

AUP では各コマンドごとに以下の処理が行われます。

  1. 必要な量のメモリがリングバッファ内で使用可能になるまで待機する
  2. ソースの .resS ファイルから、割り当てられたメモリ内にデータを読み込む
  3. ポストプロセッシング(テクスチャ解凍、メッシュコリジョン生成、プラットフォームごとの調整など)を実行する
  4. タイムスライス方式でレンダースレッドにアップロードする
  5. リングバッファメモリを解放する

複数のコマンドが同時に進行可能ですが、すべてのコマンドが 1 つの共有されたリングバッファにそれぞれ必要なメモリを割り当てる必要があります。リングバッファが一杯になると、新しいコマンドは待機することになります。この待機は非同期読み込み処理の速度を遅くしますが、メインスレッドのブロックを引き起こしたりフレームレートに影響を及ぼしたりすることはありません。

以下は、それぞれの特徴を簡単に整理したものです。

読み込みパイプラインの比較
AUP なし AUP 効果
メモリ使用 データをデフォルトヒープから読み出しながら割り当てる(ハイウォーターマーク) 固定サイズのリングバッファ ハイウォーターマークの削減
アップロード処理 データが使用可能になると同時にアップロード 固定タイムスライスで少しずつアップロード ヒッチのないアップロード
ポストプロセッシング 読み込みスレッドで実行(読み込みスレッドがブロックされる) バックグラウンドのジョブで実行 高速なローディング

ローディングパラメーターの調整に使用できるパブリック API

バージョン 2018.3 で AUP を最大限に活用するために、ランタイムで調整できる 3 つのパラメーターがあります。

  • QualitySettings.asyncUploadTimeSlice ― 各フレームで、レンダースレッドでテクスチャおよびメッシュデータの読み込みに使用される時間(ミリ秒単位)です。非同期ロード処理の進行中は、このサイズのタイムスライスを 2 つ実行します。デフォルトの値は 2 ms(2 ミリ秒)です。この値が小さすぎると、テクスチャやメッシュの GPU アップロードが遅くなる場合があります。逆に値が大きすぎるとフレームレートのヒッチが起きることがあります。
  • QualitySettings.asyncUploadBufferSize ― リングバッファのサイズ(メガバイト単位)です。各フレームでアップロードのタイムスライスが発生した場合に、タイムスライス全体を活用するための十分なデータ量がリングバッファ内に必要です。リングバッファが小さ過ぎると、アップロードタイムスライスが短縮されます。デフォルトは、Unity 2018.2 では 4MB でしたが Unity 2018.3 では 16MB に増加されました。
  • QualitySettings.asyncUploadPersistentBuffer ― Unity 2018.3 で搭載されるこのフラグは、すべての保留された読み出しが完了した時にアップロードリングバッファを解放するかどうかを設定します。このバッファの割り当てと解放はしばしばメモリの断片化を引き起こすことがあるので、通常はデフォルト(true)のままにしておくことが推奨されます。読み込み中以外でのメモリの再割り当てがどうしても必要な場合は、この値を false に設定することも可能です。

これらの設定はスクリプティング API を使うか、または Quality Settings(品質設定)メニューから調整可能です。

ワークフローの例

大量のテクスチャとメッシュを、AUP でデフォルトのタイムスライス(2 ms)およびリングバッファ(4 MB)でアップロードする場合の負荷を見てみましょう。読み込みが行われているので、1 レンダーフレームごとに 2 回のタイムスライスが入るため、アップロード時間は 4 ms 取られるはずです。しかし、プロファイラーのデータを見ると、約 1.5 ms しか使われていません。また、リングバッファ内でメモリが使用可能になっているので、アップロード直後に新しい読み出し処理が発行されているのも確認できます。このことから、より大きなリングバッファが必要であることが分かります。

試しにリングバッファを増加させてみましょう。ローディング画面なので、アップロードタイムスライスも増加させた方が良いでしょう。リングバッファを 16 MB、タイムスライスを 4 ms とすると、以下のようになります。

今度は、ほぼすべてのレンダースレッドの時間がアップロードに使用されており、アップロードとアップロードの間のわずかな時間がレンダリングに使用されていることが分かります。

以下は、この負荷サンプルでアップロードタイムスライスとリングバッファサイズの設定を変えた場合の、それぞれのロード時間を比較したグラフです。テストは MacBook Pro(2.8GHz Intel Core i7 搭載、OS X El Capitan)で実行されました。アップロード速度と入出力速度はプラットフォームやデバイスによって異なります。負荷サンプルとして使用したのは、Unity 社内でパフォーマンステストに使用されるサンプルプロジェクト『Viking Village』のサブセットです。他に読み込まれているオブジェクトがあるので、値を変更したときにどの程度パフォーマンスが改善したかは正確に確認できません。しかし、このサンプルの場合、4 MB・2 ms から 16 MB・4 ms に変更すると、テクスチャとメッシュの読み込みが少なくとも 2 倍以上速くなることは確実です。

各パラメーターを変更してみると、以下のような結果になります。

したがって、このサンプルプロジェクトでロード時間を最適化できる設定は以下のようになります。

まとめとヒント

テクスチャとメッシュの読み込み速度を最適化するときの推奨事項は下記のとおりです。

  • フレーム落ちを発生させない範囲で、最も大きい QualitySettings.asyncUploadTimeSlice を設定する。
  • ローディング画面中、一時的に QualitySettings.asyncUploadTimeSlice を増加させる。
  • プロファイラーでタイムスライスの使用状況を調査する。タイムスライスはプロファイラー上で AsyncUploadManager.AsyncResourceUpload として表示される。タイムスライスがフル活用されていない場合は QualitySettings.asyncUploadBufferSize を増加させる。
  • 基本的に QualitySettings.asyncUploadBufferSize が大きいほうが速度が上がるので、メモリが十分にある場合は 16 MB から 32 MB に増加させる。
  • ローディング中以外にどうしてもランタイム使用メモリを削減しなければならない特別な理由がなければ、QualitySettings.asyncUploadPersistentBuffer の設定を true のままにする。

よくある質問

Q: タイムスライスされたアップロードがレンダースレッドで起こる頻度はどの程度ですか?

  • タイムスライスされたアップロードは、レンダーフレームごとに 1 回、あるいは 1 回の非同期ロード処理中に 2 回発生します。VSync はこのパイプラインに影響を及ぼします。レンダースレッドが VSync を待機している間にアップロードを行えます。各フレーム 16 ms で実行していて、あるフレームが長く(例えば 17 ms に)なった場合、Vsync の待機時間は 15 ms になります。基本的に、フレームレートが高いほどアップロードタイムスライスの発生頻度が高くなります。

Q: AUP 経由で読み込まれるものは何ですか?

  • 読み込み/書き込み有効でないテクスチャが AUP でアップロードされます。
  • バージョン 2018.2 の時点では、テクスチャミップマップが AUP 経由でストリーミングされます。
  • バージョン 2018.3 では、メッシュも(非圧縮で、かつ読み込み/書き込み有効でない場合のみ)AUP 経由でアップロードされます。

Q: 非常に大きなテクスチャをアップロードする場合など、アップロードされるデータに対して、リングバッファの大きさが足りない場合はどうなりますか?

  • リングバッファより大きなアップロードコマンドは、リングバッファが完全に消費されるまで待機します。その後、必要な大きさに合わせてリングバッファが再度割り当てされます。アップロード完了後、リングバッファが元のサイズに再度割り当てられます。

Q: 同期ロード API(Resources.Load, AssetBundle.LoadAsset など)はどのように機能しますか?

  • 同期ローディングコールは AUP を使用し、非同期アップロード処理が完了するまで実質的にメインスレッドをブロックします。使用される読み込み API の種類は関係ありません。

ご意見をお寄せください

皆様のフィードバックをお待ちしています。本ページのコメント欄か Unity 2018.3 ベータ版のフォーラムから、ぜひご意見をお寄せください!

32 コメント

コメントの配信登録

返信する

これらの HTML タグや属性を使用できます: <a href=""> <b> <code> <pre>

  1. If you update the application with windows modules installer worker high cpu it can be possible to go with the update of the processor, and with that, you can get the update regarding the template, with the shotgun template.

  2. Thank you for good information.
    I want to apply this but I can’t find asyncUpload info in profiler.
    async mesh upload, resource upload etc I can’t see anything.
    I use LoadSceneAsync in coroutine.
    Is there setting to use async upload?
    I use Unity 2018.3.0b9 and 2017.3.1p4
    Thank you

  3. Thank you for good information.
    I want to apply this but I can’t find asyncUpload info in profiler.
    async mesh upload, resource upload etc I can’t see anything.
    I use LoadSceneAsync in coroutine.
    Is there setting to use async upload?
    I use Unity 2018.3.0b9 and 2017.3.1p4
    Thank you

  4. Still confused with time slice , is there any useful resource to understand time slic in the asynchronous upload progress ?
    for example, why add this time slice feature, how this value affect the asynchronous upload ?what ‘s the relative btween time slice with frame rate?

  5. About the ring buffer… Do I properly understand that having a larger ring buffer would mean that stutters are more likely to occur during the upload time?

    1. Joseph Scheinberg

      11月 5, 2018 8:43 pm 返信

      Hi! Increasing the size of the ring buffer will NOT introduce stuttering. You want a large enough ring buffer so that each render thread timeslice can be fully utilized. If you’re ring buffer is small, you might consume all the pending uploads it contains before the timeslice is complete. The idea is to keep the ring buffer primed with data so the uploading step can use the entire timeslice

  6. Can this be used to Load big Textures from disk without Hickups? It would be wonderfull to Load a Texture in Async mode like mentioned here: https://feedback.unity3d.com/suggestions/async-texture2d-dot-loadimage-and-other-texture-operations?page=2

    Would this be possible?

    1. Joseph Scheinberg

      10月 11, 2018 6:32 pm 返信

      Currently the AUP is only used to load textures that were built through the build process

      1. Does this include AssetBundles, too?

        1. Joseph Scheinberg

          10月 15, 2018 5:44 pm

          Yes it does

  7. Thanks so much!
    Now big problem is Asynchronous loading shaders – when shaders appears on screen, it’s take 100-600ms on render tread on iPhone 6 – it’s totally jork game…

    You can try our project: MadOut2 BigCityOnline

    Need add possibility to load and compile shaders async too!

    1. That is because of Shader Warming. The first time your shader is used it takes time to compile/load. To mitigate this you need to prewarm them manually, durin a startup or loading. You can easily do this through ShaderVariantCollection.WarmUp(), do note not to put all of your shaders in there.
      https://docs.unity3d.com/ScriptReference/ShaderVariantCollection.WarmUp.html

  8. Robert Cummings

    10月 9, 2018 3:46 pm 返信

    Awesome feature. Question to Unity staff: how does this feature affect multi scene async loading, if it does? I would like to load in sections of my level as I move around. Also, what API is there to manage meshes and audio (if any)? Texture mip streaming was a great start but there is not much information about everything else.

    Thanks again!

    1. Joseph Scheinberg

      10月 9, 2018 7:31 pm 返信

      The AUP is used for textures and meshes whether you load a scene async or sync. With the new async uploading of mesh data, you should be able to realize faster mesh loading times in most cases (especially if your meshes have collision) as well as smoother uploading since the uploads are time sliced over multiple frames. So this should be good for your case of loading/unloading in sub-scenes during gameplay.

      Once the Mesh data is loaded through the AUP, it stays loaded until the Mesh object is unloaded. At the moment you can’t stream the mesh data in and out while keeping the Mesh object loaded. So you might want to load your sections asynchronously as individual scenes.

      1. Robert Cummings

        10月 10, 2018 4:39 pm 返信

        Thank you for the informative reply, Joseph! I currently have designed the game around the concept of just streaming in a village or other event when triggered by culling groups API. Due to a major lack of information around the whole subject I suppose I need a guide on best practises for managing streaming in cases like this. Mesh data. Collision data, audio, probes and so on!

        Thanks for your hard work on this.

  9. Hi,

    thank you very much for this post, great information!

    You wrote “the higher the frame rate, the more frequently upload time slices will occur”. Does this mean if I turn off VSync and set the applicationTargetFramerate as high as possible, it affects the loading time in a positive fashion?

    I’m asking, because I did the exact opposite. I reduced framerate to 20fps and turned on VSync during loading screens, thinking it would give Unity more resources to actually load scenes, assets and integrate those faster. I thought I trade faster loading for more hiccups in framerate.

    Thanks for your answer in advance.

    1. Joseph Scheinberg

      10月 9, 2018 3:31 pm 返信

      Hi Peter,

      That’s correct. You can give more time for mesh/texture uploading by increasing the timeslice or increasing the framerate or both. Reducing your framerate to 20fps would likely make your loading times worse as you concluded. As an experiment you might try measuring loading times after turning vsync off and increases both your buffer size and time slice.

      1. Hi Joseph,

        thanks for the answer.

        I gave it a try. Turned vsync off, increased buffer from 4 to 16 and time slice from 2 to 4 and 8. It didn’t affect loading times, to my surprise. The scene I tested had about 250 unique meshes and 500 unique textures, mostly splat- and alpha maps from terrain.

        I tested this with Unity 2017.4.9f1 on Console.

        1. Joseph Scheinberg

          10月 11, 2018 6:12 pm

          Sounds like your loading speeds aren’t bottlenecked on the async upload pipeline. It’s difficult to diagnose the performance problem without more info. Prior to 2018.3 however, the terrain system would make all referenced textures readable, and as a result they can’t use the async upload pipeline. In 2018.3 the terrain code was refactored and it no longer marks textures as readable.

        2. Joseph Scheinberg

          10月 11, 2018 6:30 pm

          Sounds like your loading speeds aren’t bottlenecked on the async upload pipeline. It’s difficult to diagnose the performance problem without more info. Prior to 2018.3 however, the terrain system would make all referenced textures readable, and as a result they can’t use the async upload pipeline. In 2018.3 the terrain code was refactored and it no longer marks textures as readable.

  10. What about triggering the upload of resources? Is this still bound to renderers becoming visible on cameras and does it still require “trick”s like rendering one frame behind a full-screen overlay? Or are there proper APIs for ensuring resources that I know will be needed can be loaded/uploaded completely during a loading screen?

    1. Joseph Scheinberg

      10月 9, 2018 3:11 pm 返信

      When textures and meshes are loaded with any loading API, their data will be uploaded to the GPU through the AUP. This is independent of Renderer components in your scene. mipmap streaming on the other hand is influenced by Renderer objects in the scene, and you do have to perform an update before they will start streaming in. More information on mipmap streaming can be found here: https://docs.google.com/document/d/1P3OUoQ_y6Iu9vKcI5B3Vs2kWhQYSXe02h6YrkDcEpGM/edit#heading=h.d2nucy9ys0gh

      1. Hey Joseph, when you said “When textures and meshes are loaded with any loading API, their data will be uploaded to the GPU through the AUP. This is independent of Renderer components in your scene.” — Is this new behavior to 2018.3? I ask because I am using 2018.2: During a loading screen, I load a GameObject with large textures via an AssetBundle, and spawn it in the scene. There is a noticeable hiccup the first time the user looks at the GameObject. Would I expect this hitch to go away by upgrading to 2018.3?

  11. as console developer who stock with unity 2017 … it pisses me of that all the cool stuff is only unity 2018 without any support for 2017 make regret that i didn’t switch to unreal engine

    1. The updates in UE 4.21 are not in UE 4.20… just saying….
      Happy coding ;)

      1. they offer work arounds unlike unity

        1. Uh… how does it screw over older versions when you can just… download all the previous versions if you want to?

    2. Just update to 2018?

    3. Soooo… if I understand your post… are you saying : “INJUSTICE!!! I picked and am clinging on an older version of your software and it doesn’t do what the newer version does – you suck, It would never happen in other superior engines, I demand support for everything everywhere “. Dude, either get with the newer versions or go to Unreal and deal with their pros and cons there- porting your project to a newer version of Unity will probably be easier on your soul than porting it to Unreal… You are always limited to the functionality offered in your version, maybe sometimes you get support later on, but they are primarily trying to make new stuff and implement it in the newer versions and move forward so you can get new S*!t in the newer versions FASTER and more robust instead of using countless resources focusing on compatibility issues for people using outdated versions. Maybe you wanna work on your new VR project and you will chose Unity 4.2, but you want job system and ECS and VR and basically everything? And I bet If I go find and install several versions of Unreal I will probably find dosens of examples with similar/same problems. I’m not sure why your post triggered me but I find your logic a bit faulty. Or just perhaps Unity might be looking for people that are willing to make packages to offer support of new functionalities for older versions and by what I read you are the right man/woman/apache for the job.

      1. unity is not up to date on console we are way behind you can’t use the latest version

        1. Any source to back this up? First time I hear about it but I haven’t dabbled in consoles with Unity yet.