Comments
Description
Transcript
講演ファイル(PDF)
問題 速いのはどっち? Mathf.Sqrt(100f); (float)System.Math.Sqrt((double)100f); 速いのはどっち? Mathf.Sqrt(100f); (float)System.Math.Sqrt((double)100f); ←こっち あと読み用メモ • 計測方法は、Unity Editor(Mac)にてター ゲットの式を for 文にて大量に繰り返し て経過時間を計測 • 傾向はつかめるが正確な測定ではない • プラットフォームごとに異なる可能性 • Mono(AOT)の動作。IL2CPPはわからない • 数回繰り返して安定して差が現れている // 計測コードの例 var sw = new System.Diagnostics.Stopwatch(); sw.Start(); { long begin_time = sw.ElapsedTicks; for (var i = 0; i < 1000000; ++i) { float f = Mathf.Sqrt(100f); } Debug.Log("elapsed:"+ (sw.ElapsedTicks - begin_time)); } { long begin_time = sw.ElapsedTicks; for (var i = 0; i < 1000000; ++i) { float f = (float)System.Math.Sqrt((double)100f); } Debug.Log("elapsed:"+ (sw.ElapsedTicks - begin_time)); } 第2問 速いのはどっち?(Vector3) v += v1; v.x += v1.x; v.y += v1.y; v.z += v1.z; 速いのはどっち?(Vector3) v += v1; v.x += v1.x; v.y += v1.y; v.z += v1.z; ←こっち 速いのはどっち?(Vector3) v += v1; C#には operator+= がないので v = v + v1 が実行され、 オブジェクトの生成が入る v.x += v1.x; v.y += v1.y; v.z += v1.z; bad-knowhow… 基本的にネタです • コードは可読性も重要 • プロファイルしない最適化はだいたい無意味(例外として、コーディングの時点で深いル ープに気付いている場合) • monoの動作を知っておこう、という話です 第3問 速いのはどっち? void func(Vector3 a) { … } void func(ref Vector a) { …. } 速いのはどっち? void func(Vector3 a) { … } void func(ref Vector a) { …. } ←こっち 速いのはどっち? void set(ref Vector3 a) { m_position = a; } void set(ref Vector a) { m_position.x = a.x; m_position.y = a.y; m_position.z = a.z; } 速いのはどっち? void set(ref Vector3 a) { m_position = a; } void set(ref Vector a) { m_position.x = a.x; m_position.y = a.y; m_position.z = a.z; } ←こっち 速いのはどっち? transform.position = Vector3.zero; transform.position = new Vector3(0f, 0f, 0f); 速いのはどっち? transform.position = Vector3.zero; transform.position = new Vector3(0f, 0f, 0f); ←こっち さらに言うとこのほうが速い Class CV { public static Vector3 zero = Vector3.zero; } transform.position = CV.zero; さらに言うとこのほうが速い ref をつけたいので readonly に しない Class CV { public static Vector3 zero = Vector3.zero; } transform.position = CV.zero; bad-knowhow… ハードウェア性能を引き出して 60fps を実現する プログラミング・テクニック ユニティ・テクノロジーズ・ジャパン フィールドエンジニア 安原 祐二(やすはら ゆうじ) 16.666ms でゲームを作ってみた ゲームを作るにあたって ゲームを作るにあたって • メインターゲットは PlayStationⓇVita (PS Vita) • 2011年発売 ゲームを作るにあたって • メインターゲットは PlayStationⓇVita (PS Vita) • プラグインは書かない • ぜんぶC#、プラットフォーム依存コードも書かない ゲームを作るにあたって • メインターゲットは PlayStationⓇVita (PS Vita) • プラグインは書かない • リリース済みのUnityを使用する • 皆さんと同条件。開発開始時は5.3.3p1 ゲームを作るにあたって • メインターゲットは PlayStationⓇVita (PS Vita) • プラグインは書かない • リリース済みのUnityを使用する • 製品のつもりで作る • 実際の製品プロジェクトでの実現可能性を重視 “Survival Shooter” をPS Vitaで実行してみる (Quality Settings で Fastest に設定) Survival Shooter デモ “Survival Shooter” をPS Vitaで実行してみる (Quality Settings で Fastest に設定) Survival Shooter デモ 12~13FPS…! 低いスペックから開始する試み • 性能が高い方から低い方へのポーティングは困難 • ゲームの仕様もスペックを考えて決める • 同じリソースでいかに効果を高めるか 3つの工夫 3つの工夫 1.ゲームロジックを別スレッドで実行する 3つの工夫 1.ゲームロジックを別スレッドで実行する 2.GPUを多用する 3つの工夫 1.ゲームロジックを別スレッドで実行する 2.GPUを多用する 3.動的なメモリ確保をしない 1.ゲームロジックを別スレッドで実行する • コアを使い切れていない(PS Vitaは4コア) • Main Thread への負荷集中 16.666ms 16.666ms 16.666ms Main Thread 10 11 12 Render Thread 9 10 11 GPU 8 9 10 16.666ms 16.666ms 16.666ms Main Thread 10 11 12 Render Thread 9 10 11 GPU 8 9 10 16.666ms 16.666ms 16.666ms Main Thread 10 11 12 Render Thread 9 10 11 GPU 8 9 10 ほとんどの場合 Main Thread が問題になる • InputProcess(206.937us) • PlayerLoop • InputManager::ProcessInput • PhysicsManager::InitializeClass • PhysicsManager::FixedUpdate(785.874us) • Physics2DState::Initialize • GUIManager::InitGUIManager • InitializeNavMeshManager • SpeedTreeWindManager::SpeedTreeWindManager(3.048us) • BehaviourManager::Update(7.387ms) • AudioModule::Update(226.066us) • PhysicsManager::InitializeClass()::PhysicsSkinnedClothUpdate(3.201us) • ParticleSystem::InitializeClass()(1.673us) • [local to EnlightenRuntimeManager_cpp]::UpdateEnlightenRuntimeManager(1.763us) • SkinnedMeshRenderer::UpdateAllSkinnedMeshes(7.628us) • DirectorManager::InitializeClass()::DirectorRenderImage(2.066us) • UI::InitializeCanvasManager()::CanvasManagerEmitWorldGeometry(7.787us) • UI::InitializeCanvasManager()::EmitScreenSpaceCameraGeometry(3.297us) • PlayerRender(3.696ms) • Camera::Cull(329.673us) • Camera::Render(26us) • Camera::Render • RenderManager::RenderOffscreenCamera • RenderManager::RenderCameras • Camera::Cull • Camera::Render • GfxDeviceContext::BeginFrame(wait for vsync) • Camera::DoRender DoRenderLoop • Unity::Component::SendMessageAny(here means OnPostRender:29.862us) • Main Thread Main Thread 1フレームの タイムライン(模式図) 16.666ms 基本料金 ユーザコード レンダリング V Blank 同期 処理量が増えると・・ アウト! 16.666ms 基本料金 ユーザコード 増加 レンダリング 増加 そこで ゲーム処理を別スレッドへ 16.666ms ユーザコード 16.666ms 基本料金 レンダリング V Blank 同期 16.666ms 16.666ms 16.666ms 11 12 13 Main Thread 10 11 12 Render Thread 9 10 11 GPU 8 9 10 Update Thread ! New private void thread_entry() { for (;;) { // ゲーム処理 } } void Start() { update_thread_ = new Thread(thread_entry); update_thread_.Start(); } 別スレッドの問題 別スレッドの問題 • MainThreadに情報を渡す経路が必要 別スレッドの問題 • MainThreadに情報を渡す経路が必要 ダブルバッファでがんばる 別スレッドの問題 • ゲームオブジェクト(MonoBehaviour)の機能(Updateな ど)が使えない 別スレッドの問題 • ゲームオブジェクト(MonoBehaviour)の機能(Updateな ど)が使えない タスク的な構造を自作する 別スレッドの問題 • ほとんどの Unity API は Main Thread でしか呼べない (呼ぶと Assert で失敗する) 別スレッドの問題 • ほとんどの Unity API は Main Thread でしか呼べない (呼ぶと Assert で失敗する) 呼ばない 別スレッドの問題 • コリジョンどうすんの 別スレッドの問題 • コリジョンどうすんの 自作する 別スレッドの問題 • 物理どうすんの 別スレッドの問題 • 物理どうすんの 自作する 別スレッドの問題 • その他もろもろ 別スレッドの問題 • その他もろもろ がんばる ダブルバッファ解説 16.666ms ユーザコード 16.666ms 基本料金 レンダリング V Blank 同期 Update Thread には2つのフェーズ 16.666ms update(ゲーム処理) renderUpdate ダブルバッファ 16.666ms update(ゲーム処理) renderUpdate バッファの内容 バッファA ・オブジェクトの座標 (Transform相当)、種類 バッファB ・GPU処理用頂点群 ・サウンド(効果音) Update Thread & Main Thread 16.666ms 16.666ms updateでゲーム処理 ・各種ゲーム動作 update renderUpdate update renderUpdate バッファA バッファA バッファB バッファB 基 render レンダリング VSync ・物理、コリジョン ・バッファは触らない! renderUpdateでバッファ作成 Update Thread & Main Thread 16.666ms update 16.666ms renderUpdate update renderUpdate renderでUnityAPIコール ・Transform setup バッファA バッファA バッファB バッファB 基 render レンダリング VSync ・Mesh update ・AudioSource.Play あるオブジェクトに フォーカス フレームごとにどのオブ ジェクトになるか決まる ダブルバッファでメモリは大丈夫なの? ダブルバッファでメモリは大丈夫なの? • • • • • • • • DrawBuffer:16K+ Beam:54K+ Spark:60K+ Explosion:5K+ Hahen:48K+ Trail:96K+ Sprite:20K+ Sight:3K+ 1Mにも満たない 2.GPUを多用する • たくさん出して賑やかにする • ポストエフェクト(ブルーム) • https://bitbucket.org/Unity-Technologies/cinematic-imageeffects トレイル 火花 弾 爆発 破片 レーザー スプライト 粒子 ブルーム GPUパーティクル • Mesh.vertices を更新して描画 • MeshRenderer使用(DrawMeshと比較して採用) • 同種のオブジェクトを一度に描画する • なるべくシェーダに記述する 爆発の場合 vertices オブジェクト ひとつ4頂点 uv uv2 爆発発生時 発生位置を4点に 回転角度と発生時刻を4点に vertices uv 固 定 インデクスを進める uv2 Main Thread setup 毎フレームMaterialにセット ・カメラのupベクトル ・現在時刻 vertices uv uv2 インデクスは単純ループ インデクスを進める • 古い爆発は上書きされる vertices uv uv2 常に全てを描画する • 生き死には気にしない vertices uv uv2 問題点 • Mesh.vertices 代入が重い (normals も uv も全て) • DrawMesh を個別に発行する方式も実装してみた が、Mesh.vertices にまとめるほうがマシだった • 現状、Main Thread の Update 負荷はほぼコレ 楕円体シェーダ ParticleSystem の Stretch Billboard きつい角度で細くなってしまう 楕円体シェーダ あらゆる角度で破綻しない 火花シェーダ 実態以上に 高フレームレートに 見せる 裏ワザ • 配列のシェーダ定数に値を渡す方法がある • 今は undocumented だけど 5.4 で正式なものに • 頂点ごとの属性をインデクス管理可能になる • 頂点のカラー設定を撤廃できた! 3.動的なメモリ確保をしない • struct を多用 • オブジェクトの事前確保・使い回し GC.Collect を避ける • 難しい! 動的な mono heap は極力使わない • 使うぶんは最初に確保 • ゲームループ中の明示的な class new はゼロ • しかし完全排除は難しい 使われてしまう mono heap • foreach 文でmono heapを使ってしまう • IEnumerator は class だから • 今回 foreach は使用せず • LinkedList.Add で LinkedListNode を確保してしまう (はず) 使ってしまった mono heap • Coroutine でゲームを書きたい • IEnumerator で mono heap を使ってしまう 使ってしまった mono heap • MonoBehaviourなしでコルーチンを呼ぶ アクション定義 public IEnumerator zako_act() { … // 登場時の処理 yield return null; for (var i =new Utility.WaitForSeconds(10f, time_); !i.end(time_);) { … // 10秒間の動作 yield return null; } destroy(); // 消滅 } アクション呼び出し public void init() { enumerator_ = zako_act(); } public void update() { enumerator_.MoveNext(); } GC の制御は非常に難しい • 今回もGCは発生している(10分に1回ぐらい) • が、負荷スパイクは発生していない • 事前拡張(詳細略)していないからGC対象が狭い • ゲーム仕様で適宜ゲームを中断させる(ブリーフィングを 入れるとか)しかない・・かも 4.その他のtips パフォーマンスメーターを出す パフォーマンスメーターを出す • Update Thread はカンタン(すべて掌握している) • 問題は Main Thread と Render Threadと GPU パフォーマンスメーターの意味 update loop renderUpdate loop MonoBehaviour.Update Update Thread Main Thread frame count GC count number of Objects Main Thread 16.666ms 処理 V Blank 待ち Main Thread 16.666ms 処理 16.666ms V待ち 処理 V待ち 16.666ms 処理 16.666ms V 処理 16.666ms V 処理 V Main Thread 16.666ms 処理 16.666ms V待ち 処理 V待ち 16.666ms 処理 16.666ms V 処理 16.666ms V 処理 V V Blankの終了と開始に近いコールバックを探す! PS4, PS Vita の場合 16.666ms 16.666ms 16.666ms 16.666ms 16.666ms PlayerRender 処理 V待ち 処理 V待ち 処理 PlayerRender中にV Blank同期がある V 処理 V 処理 V PS4, PS Vita の場合 16.666ms 16.666ms 16.666ms 16.666ms 16.666ms PlayerRender 処理 V待ち 処理 V待ち 処理 OnPreCull V 処理 V OnPreRender もっとも近くにあるコールバックがこのふたつ 処理 V PS4, PS Vita の場合 16.666ms 処理 16.666ms V待ち 処理 V待ち 16.666ms 処理 16.666ms V OnPreRender OnPreCull シーン中、最初に処理するカメラの OnPreRender で計測開始 OnPreCull で計測終了 処理 16.666ms V 処理 V PC, iOS, Androidの場合 (PS Vita ほど調べられていない) 16.666ms 処理 16.666ms V待ち 処理 V待ち 16.666ms V 処理 MonoBehaviour.Update 16.666ms 処理 OnPostRender 最初に処理する MonoBehaviour.Update で計測開始 最後に処理するカメラの OnPostRender で計測終了 (計測できていない部分がけっこうある) 16.666ms V 処理 V パフォーマンスメーター • Render Thread, GPUは計測困難 • 将来の Unity に期待しましょう • ま、問題になるのは Main Thread なので GC.Collect カウンタを出す GC.Collect カウンタを出す • System.GC.Collection.Count(0 /* generation */) を表示するだけ まとめ:Unityを使う利点 「そんなに自作して、 Unity を使う意味あるの?」 あります! ポーズメニューが一瞬で完成 ポーズメニューが一瞬で完成 せっかく停止しているので、 抜けるときは GC.Collectを 呼んでおく その他、Unityを使う利点 • レンダリングエンジン • ビルドパイプライン • 統合環境 • エディタ実行 • アセット配置 • マルチプラットフォーム 結果 : PS Vita でのパフォーマンス • Main Thread が12msec前後 16.666ms まだ余裕がある 結果 : PS Vita でのパフォーマンス • これだけ余ってれば、製品化まで持っていけそう 参考 : PS4 でのパフォーマンス • Main Thread が2msec以下 16.666ms おまけ:Physx を削る • このプロジェクトではPhysxを一切使用していない • InputProcess(206.937us) • PlayerLoop • InputManager::ProcessInput • PhysicsManager::InitializeClass • PhysicsManager::FixedUpdate(785.874us) • Physics2DState::Initialize • GUIManager::InitGUIManager • InitializeNavMeshManager • SpeedTreeWindManager::SpeedTreeWindManager(3.048us) • BehaviourManager::Update(7.387ms) • AudioModule::Update(226.066us) • PhysicsManager::InitializeClass()::PhysicsSkinnedClothUpdate(3.201us) • ParticleSystem::InitializeClass()(1.673us) • [local to EnlightenRuntimeManager_cpp]::UpdateEnlightenRuntimeManager(1.763us) • SkinnedMeshRenderer::UpdateAllSkinnedMeshes(7.628us) • DirectorManager::InitializeClass()::DirectorRenderImage(2.066us) • UI::InitializeCanvasManager()::CanvasManagerEmitWorldGeometry(7.787us) • UI::InitializeCanvasManager()::EmitScreenSpaceCameraGeometry(3.297us) • PlayerRender(3.696ms) • Camera::Cull(329.673us) • Camera::Render(26us) • Camera::Render • RenderManager::RenderOffscreenCamera • RenderManager::RenderCameras • Camera::Cull • Camera::Render • GfxDeviceContext::BeginFrame(wait for vsync) • Camera::DoRender DoRenderLoop • Unity::Component::SendMessageAny(here means OnPostRender:29.862us) • • InputProcess(206.937us) • PlayerLoop • InputManager::ProcessInput • PhysicsManager::InitializeClass • PhysicsManager::FixedUpdate(785.874us) Time Settings→Fixed Tilmestepで制御する • Physics2DState::Initialize • GUIManager::InitGUIManager • InitializeNavMeshManager • SpeedTreeWindManager::SpeedTreeWindManager(3.048us) • BehaviourManager::Update(7.387ms) • AudioModule::Update(226.066us) • PhysicsManager::InitializeClass()::PhysicsSkinnedClothUpdate(3.201us) • ParticleSystem::InitializeClass()(1.673us) • [local to EnlightenRuntimeManager_cpp]::UpdateEnlightenRuntimeManager(1.763us) • SkinnedMeshRenderer::UpdateAllSkinnedMeshes(7.628us) • DirectorManager::InitializeClass()::DirectorRenderImage(2.066us) • UI::InitializeCanvasManager()::CanvasManagerEmitWorldGeometry(7.787us) • UI::InitializeCanvasManager()::EmitScreenSpaceCameraGeometry(3.297us) • PlayerRender(3.696ms) • Camera::Cull(329.673us) • Camera::Render(26us) • Camera::Render • RenderManager::RenderOffscreenCamera • RenderManager::RenderCameras • Camera::Cull • Camera::Render • GfxDeviceContext::BeginFrame(wait for vsync) • Camera::DoRender DoRenderLoop • Unity::Component::SendMessageAny(here means OnPostRender:29.862us) • おまけ:Physx を削る • Time Settings→Fixed TimeStep は最初に設定すべき項目! • デフォルトで 0.02( の50fps)なので、30fps想定なら 0.0333 にしておきましょう • 今回は最大値の10にした(もっと大きな値にできたら嬉し いのだけど) • InputProcess(206.937us) • PlayerLoop • InputManager::ProcessInput • PhysicsManager::InitializeClass 減った! • PhysicsManager::FixedUpdate(785.874us -> 474.474ns) • Physics2DState::Initialize • GUIManager::InitGUIManager • InitializeNavMeshManager • SpeedTreeWindManager::SpeedTreeWindManager(3.048us) • BehaviourManager::Update(7.387ms) • AudioModule::Update(226.066us) • PhysicsManager::InitializeClass()::PhysicsSkinnedClothUpdate(3.201us) • ParticleSystem::InitializeClass()(1.673us) • [local to EnlightenRuntimeManager_cpp]::UpdateEnlightenRuntimeManager(1.763us) • SkinnedMeshRenderer::UpdateAllSkinnedMeshes(7.628us) • DirectorManager::InitializeClass()::DirectorRenderImage(2.066us) • UI::InitializeCanvasManager()::CanvasManagerEmitWorldGeometry(7.787us) • UI::InitializeCanvasManager()::EmitScreenSpaceCameraGeometry(3.297us) • PlayerRender(3.696ms) • Camera::Cull(329.673us) • Camera::Render(26us) • Camera::Render • RenderManager::RenderOffscreenCamera • RenderManager::RenderCameras • Camera::Cull • Camera::Render • GfxDeviceContext::BeginFrame(wait for vsync) • Camera::DoRender DoRenderLoop • Unity::Component::SendMessageAny(here means OnPostRender:29.862us) • おまけ:Camera::Cull が(ときどき)高負荷に • system call には注意 • デバイスのアクセスは問題なし(Input とか VSync とか) • 注意すべきは同期オブジェクトの調停 • InputProcess(206.937us) • PlayerLoop • InputManager::ProcessInput (オレンジ色はSystemCallを呼んでいる関数) • PhysicsManager::InitializeClass • PhysicsManager::FixedUpdate(785.874us) • Physics2DState::Initialize • GUIManager::InitGUIManager • InitializeNavMeshManager • SpeedTreeWindManager::SpeedTreeWindManager(3.048us) • BehaviourManager::Update(7.387ms) • AudioModule::Update(226.066us) • PhysicsManager::InitializeClass()::PhysicsSkinnedClothUpdate(3.201us) • ParticleSystem::InitializeClass()(1.673us) • [local to EnlightenRuntimeManager_cpp]::UpdateEnlightenRuntimeManager(1.763us) • SkinnedMeshRenderer::UpdateAllSkinnedMeshes(7.628us) • DirectorManager::InitializeClass()::DirectorRenderImage(2.066us) • UI::InitializeCanvasManager()::CanvasManagerEmitWorldGeometry(7.787us) • UI::InitializeCanvasManager()::EmitScreenSpaceCameraGeometry(3.297us) • PlayerRender(3.696ms) • Camera::Cull(329.673us) • Camera::Render(26us) • Camera::Render • RenderManager::RenderOffscreenCamera • RenderManager::RenderCameras • Camera::Cull 同期オブジェクトを使ってるぽい • Camera::Render • GfxDeviceContext::BeginFrame(wait for vsync) • Camera::DoRender DoRenderLoop • Unity::Component::SendMessageAny(here means OnPostRender:29.862us) • おまけ:Camera::Cull が(ときどき)高負荷に • Occlusion Culling が複数スレッドで実行される模様 • ある程度の量があれば高速化するが、そうでない場合 はオーバーヘッドが大きい • 使わないなら Camera の Occlusion Culling を切ってお く • 負荷スパイクは発生しなくなった おまけ:AudioSource.PlayOneShot が重い • こういう「メモリ渡す系」は疑ったほうがよい • (clipが解放されてもよいように中で複製してそう・・) • 対策としてAudioClipそれぞれに対応したAudioSourceを生成・保持して Play を呼ぶ • AudioSourceを山ほど作ることに • どのみちFMOD処理はMainThreadからオフロードされているので酷使してやる • ついでに同じ音が重なる場合(爆発など)に備えて同じAudioClipにつき複数の AudioSourceを使って重ねる(FMODをさらに酷使) もっといい方法 ないですかね・・ • InputProcess(206.937us) • PlayerLoop • InputManager::ProcessInput (オレンジ色はSystemCallを呼んでいる関数) • PhysicsManager::InitializeClass • PhysicsManager::FixedUpdate(785.874us) • Physics2DState::Initialize • GUIManager::InitGUIManager • InitializeNavMeshManager • SpeedTreeWindManager::SpeedTreeWindManager(3.048us) • BehaviourManager::Update(7.387ms) • AudioModule::Update(2.262ms->226.066us) 減った! • PhysicsManager::InitializeClass()::PhysicsSkinnedClothUpdate(3.201us) • ParticleSystem::InitializeClass()(1.673us) • [local to EnlightenRuntimeManager_cpp]::UpdateEnlightenRuntimeManager(1.763us) • SkinnedMeshRenderer::UpdateAllSkinnedMeshes(7.628us) • DirectorManager::InitializeClass()::DirectorRenderImage(2.066us) • UI::InitializeCanvasManager()::CanvasManagerEmitWorldGeometry(7.787us) • UI::InitializeCanvasManager()::EmitScreenSpaceCameraGeometry(3.297us) • PlayerRender(3.696ms) • Camera::Cull(329.673us) • Camera::Render(26us) • Camera::Render • RenderManager::RenderOffscreenCamera • RenderManager::RenderCameras • Camera::Cull • Camera::Render • GfxDeviceContext::BeginFrame(wait for vsync) • Camera::DoRender DoRenderLoop • Unity::Component::SendMessageAny(here means OnPostRender:29.862us) • おまけ:IL2CPPは • 高速化するコードもあれば、しないコードもある • 経験的に、通常のプロジェクトではあまり効かない が、今回のプロジェクトは • 2倍ほど高速化した(予想通り) • ただし Main Thread には効かなかった(予想通り) IL2CPPでこれだけ高速化するなら • update を複数回まわしても大丈夫かも • Δt を小さくすると物理計算の諸問題を回避できる • 高速な移動物体の貫通問題 • 強い摩擦、バネ係数での発散問題 https://github.com/unity3d-jp/ AnotherThread