なんとかするから、なんとかなる

エンジニア関係のことを書きます

Unite2017 参加しました その6

C#ジョブシステムによるモバイルゲームのパフォーマンス向上テクニック

www.slideshare.net

はじめに

  • これからの方向性を示した内容。まだまだ変わる可能性がある。

Jobs101

  • マルチコアのハードにきちんと対応するためにJob化がされる。
  • ただしマルチコアに対応するために並列処理をきちんと意識する必要がある。
  • Unity2017.2 以降で対応予定

2017以降の新機能

  • C++とC#間ではmarshalingなしでアクセスできるNativeArrayの型ができる
  • jobに最適化された新たなコンパイラができる

    • jobSystemではGCが働かないunmanagedなので、developperがきちんとDisposeする必要がある。
    • これにより、マルチスレッドにより特化したコーディングが可能となる。
  • MONOのコンパイル処理が変わる

    • ILから中間言語を出して、最適化そしてLLVM送りさらに最適化、そしてメモリに書き込む。

現在のマルチスレッドに対する制限

  • Jobをまだ止めることができない。
  • スレッドセーフではない

job化のbest practice

  • DataOrientedDesign(DOD)
  • マルチスレッドに対応していなくもDODを適応すべき
  • Objectをまとめて同じスクリプトを適応するよりも、Arrayにつめてマネージャーへ

DODへの方法

Step1(従来のMonoBehavior追加する方法)

MonoBehaviorに対して関数を追加して使う

Step2

オブジェクトのすべてのtransformをArrayで管理する。

Step3

IComponentDataを利用して、各Objectの情報を格納する

Step4

データの整合性をとれるようにする。

Unite2017 参加しました その5

シェーダープログラミング入門!カスタムシェーダー、作るで!

www.slideshare.net

rendering pipeline

どのようにして、Unityがレンダリングを実行するか

shaderを書くための言語

  • CG(C for Graphics) Sessionで使われてた言語はこれ。 C言語に似た言語

  • Shader Lab

CG(C for Graphics)の見方

v2f

v2fではvertexをCG(C for Graphics)で使えるようにするための構造体

Properties

Propertiesを利用することで、UnityのInspectorから値を指定することができる。 詳しくは参考URLのサンプルプログラムShader4

Shaderのタイプは大きく分け3つ

それぞれ表現方法によってどれを使うかは異なる。 基本的な使い方は、subshader の中にそれぞれに対応する関数を作ってあげる。

  • Vertex Sharders
  • Fragment Shaders
  • Surface Shaders

Vertex Shaders

Vertex Shadersでは頂点を入力に対して、頂点を位置を変更して出力する。 Shaderで動きをつけたいときに使える。

flaot vert(appdata_base v)

頂点座標に対して処理を実行する

  • 引数 appdata_base 頂点(vertex), 法線(normal), UV情報(texcorrd)が含まれている。 より詳細な情報(UV2)などは、appdata_fullという構造体に含まれている。

v.vertexに対して、倍率を指定すると拡大/縮小して表示することがができる。 時間などの係数をかけることで、簡単なモーションを表現することもできる。

Fragment Shaders

Pixcel 1つ1つに対して、色やAlpha値を変更するときに使える。 色彩の表現を付け加えるときに便利。

half4 frag(v2f i)

この関数のなかで各ピクセルに対して処理を実行することができる。

Sessionでは、この関数で以下を返すことで、Shaderが適応されたオブジェクト赤色になっていた。

return half4(1,0,0,1)

Surface Shaders

Pixel 1つ1つに対する処理というよりは、面に対して何かしらの表現を付け加えるときに便利。

void surf(Input IN, inout SurfaceOutput o)

事前にテクスチャをして置き、SurfaceOutputのalbedoを指定することでテクスチャを貼り付けることができる。

o.Albedo = tex2D(_MainTexture, IN.uv_MainTexture).rgb;

tex2Dの第二引数IN.uv_MainTextureでuv座標を指定しているため、第二引数を動的に変化させることでTexutureがオブジェクトの表面を流れるような表現をすることができる。

Emmission Texture

MainTextureが全体に対するベースとなるTexutureで、EmmissionTextureその上に張ることができる。Texutureを重ねて表現するときに便利

  • 参考 その他テクスチャの張り方

    事前にテクスチャを指定しておき、vert関数でuv座標を指定、fragでその色情報を取り出すことで、テクスチャの貼り付けができる。

参考資料

github.com

デモでかなりわかりやすく各Shaderを紹介していたので、動画が待ち望まれる!

Unite2017 参加しました その4

バグを殲滅!Unityにおける実践テスト手法

www.slideshare.net

Unity のテストツール

Unity 標準テスト環境あり

  • Unity5.6 play mode
  • Untiy5.5以前 edit mode

単体テスト

NUnit

  • C#向けのテストツール
  • Unityの古めのバージョンから支えていたが、Unity5.6からNUnit3系となったっぽい

TIPS

  • MonoBehaiviorのオブジェクトはTearDownでnew する
  • GUIで指定する各Objectは引数の差し替えで追加する。
  • なので、依存関係がやばいとテストが大変

単体テスト環境の欠点

  • MonoBehaiviorのライフサイクルはテストできない
    • もしテストをするなら、MVPのPresenterのような形でライフサイクルときちんと切り出さなければならない?
  • SingletonやStaticなものは、テストを再実行してもメモリ上に残る。(明示的にイニシャライザをしてあげる)
  • Unity5.6未満だと、テストで作成されたgameObjectはテスト後でも残るので、明示的にデストラクタを呼ぶ必要がある。

結合テスト

方法

  • キャラクタなどをnewして、Positionなどを整えることで結合テストが可能となる。
  • Object間の関係をテストするのに使える。接触した場合の動作など。
  • PlayModeであれば、ライフサイクルのテストも可能

MonobehaviourTest

  • Unity5.6から使えるようになったテスト
  • ライフサイクルをスタブする場合に便利かもしれない
  • いくつかバグがすでに見つかっているので注意

その他 テストツール詳解

  • UnityTestTools
  • Integration Test Framework
  • Unity UI Test Automation

Unite2017 参加しました その3

「黒騎士と白の魔王」にみるC#で統一したサーバー/クライアント開発と現実的なUniRx使いこなし術

www.slideshare.net

開発環境

  • Perticleの関係で、IL2CPPでビルドはしているが、unity5.3.7
  • MMORPG すべてのバトルはC#サーバーにて処理
    • AI・演算・結果処理はサーバー
    • クライアントはコマンドを受けて、画面を更新

C#大統一理論

メリット

  • コードの共有が可能
  • サーバーとクライアントのエンジニア同士が互いのコードを理解できる。
  • サーバーとクライアントでエンジニアを入れ替えられる。(エンジニアのレベル向上)
  • DLLなどの中間プロジェクトを共有できる
    • SharedLibraryという中間定義をする必要があった
  • IDL(Interface Definition Language)経由の中間コード生成が不要
    • JSONなどの中間形式が不要
    • VisualStudioとの親和性が高い(リファクタやシンタックスハイライト機能が便利)

デメリット

  • エンジニアがサーバーとクライアントを合わせたチームになるため、コミュニケーションが困難
  • エンジニアには得手不得手がある。

HTTP/2

WebAPIを置き換えるために何か別のフレームワークを利用したい

gRPCとは

  • Googoleの提唱するHTTP/2のフレームワーク
  • HTTP/2は常時接続・バイナリ通信をサポート
  • gRPCはストリーミング通信をサポート
  • Unityにおいて、リアルタイム通信が可能になる!

gRPCの導入

  • gRPCはC#フレームワークなので、Unityに対応ライブラリを作った。
  • MagicOnionという名前でOSSで公開中

Battle Engine

  • WebAPI系とリアルタイム通信系の2つ
  • リアルタイム通信にはgRPCを使ったStreamingServerを実装
  • ちなみに、敵のAI部分はF#で日本語プログラミング言語を実装

UniRX

メリット

  • 通信のハンドリングとRXの親和性は高め(非同期処理向け)
    • Unity2017でasync/await が実装されるためこっちの方がオススメ

デメリット

  • リアクティブスパゲッティが完成
    • Rxと使いまくるとChaosへ。
    • 伝搬を短くしましょう。
    • 循環したらやばいので、注意する。

Tips

ToYieldInstructionの場合IObservableでEmpty返しても思い通りに動かない。 Neverを返すのがオススメ

その他

JSON.NET(Unity)のシリアライズ遅い

  • ZeroFormatter
    • FlatBuffers を真似たデシリアライザ。
    • ただし、通信上大きくメモリが辛い
  • MessagePack-C#

マスタデータの生成が遅い

  • MasterMemory
  • In memory databaseを利用することで解決

参考URL

github.com

github.com

github.com

Unite2017 参加しました その2

パフォーマンス向上のためのスクリプトのベストプラクティス

www.slideshare.net

C# Compiler

.NET4.6

動画を見直して、再度学習します。 * Unity2017が.NET4.6に対応するので、C#6.0の機能を利用することができる * 非同期処理でAsyncやTasksに対応 * 今後.NET Standard2.0にも対応予定 * .NET4.6が安定したら、新たなGCにも対応よてい

Marshalling

Unmanaged Codeとは

  • 開発者自身がオブジェクトやメモリの管理に責任を持つコード。
  • GC(Garbage Collector)の対象から外れる。
  • DLL/Librarieなどを経由してManaged Codeにアクセスすることができる。

Marshallingとは

  • C#C++ 間で、IntやStringを共有するとき、それぞれのオブジェクトに変換作業。
  • Managed codeとUnmanaged Codeを行き来するためのオーバーヘッド作業。
  • C#C++間ではMarshalling asをつけることでClassやStructの対応がとれる。
  • Memoaryのレイアウトを意識しないとパフォーマンスに大きな影響を与えてしまう。
  • Marshallingするとメモリ内でコピーをしてしまう注意が必要

Unityにおける ManagedHeap

  • Heap領域に必要に応じて、メモリを確保する。
  • Heap領域が確保できないと、Heap領域を増やす。
  • GC(Garbage Collector)は使用されなくなったオブジェクトを削除する。
  • GCはHeap領域が足りなくなった場合に、走る。それでも足りないと増やす。
  • CGによって削除された空間は、埋められることはないためFragmentのになる。
  • そして、一度Heap領域として確保されたメモリは解放されることがない。

避けるためには

  • ReuseCollectionを使う。
  • 可能な限りStringを避ける。
  • ラムダ関数やメソッドはHeapを使ってしまう。

Boxing

  • 値型が参照型を引数として渡されるときに発生する。
  • 値がheapにallocateされてしまう。(temporary heap managed allocation)
  • Fragmentを引き起こす原因になりうる。

Foreachループ

  • EnumeratorオブジェクトがHeap領域にAllocateされてしまう。すなわちHeap領域を圧迫してしまう。
  • 簡単なForeachであればコンパイラが自動でForloopに置き換える。
  • ただし、Collectionの場合はBoxingが発生してしまう。
  • 自身Enumeratorを定義するなど工夫が必要

  • UnityのAPIでもArrayを返す場合、Heap領域にallocateしてしまう。 ForLoopの中でUnityのArrayを参照する場合は、Loopの前に値をポインタに保持して、そのポインタ経由でアクセスする必要がある。(mesh.verticesやmesh.triangles 遅いのもこれが原因か。)

? Operator

Swiftエンジニアなら慣れ親しんでいるOptional(null許容) C# 6で出てきていたのですね。

参考

[Unite 2016 Tokyo]モバイル端末向けのUnityアプリケーションの最適化実践テクニック - YouTube

Unity - Manual: Understanding the managed heap

.NETの中間言語 | C#たんっ!

Unite2017 参加しました

Unity最適化講座 ~スペシャリストが教えるメモリとCPU使用率の負担最小化テクニック~

www.slideshare.net

Transformsについて

  • TransformsはすべてのGameObjectが持っている。
  • Transformsが変更されると、イベントメッセージが送られる。
  • イベントメッセージはPhysics, Render, Particleなど様々なComponentsに対して送られる。
  • Transformを変更すると、処理が高くつく

これからのTransformの変更方法

  • SetPosisionAndRotation(Unity5.6~)を利用する。 このAPIを利用することで、無駄なイベントメッセージを減らすことができる。

Transformsが多すぎる問題

  • Unityちゃん単体では150ものTransofrmsが存在する。
  • Unityちゃんを大量に設置した場合、制御するTransformsが多すぎるため、パフォーマンスは悪くなる

大量のTransformsの最適化

  • [inspector]->[Optimize Game Object] にチェックを入れる GameObejctに対するアニメーションをサブスレッドに投げることができる。 それをしないと、すべてメインスレッドで動いてします。
  • 一部のTransformsを処理から除外する。(間引く) 除外対象を[Extra Tranforms to Expose]に追加する。

Physics

Physicsの中でも、以下の2つに計算コストがとられる

  • Rigidbodyの更新など物理演算
  • Raycast

Physicsを重くするには

  • 狭い空間にたくさんのCollider(できればMeshCollider)を設置

Physicsを軽くするために

  • RaycastのMaxDistanceパラメータを設定する
  • Physics Layersを適切に設定することで、不要な処理を省く
  • 物理演算の間隔を間引く(パフォーマンスと精度のトレードオフ) [Inspector]->[TimeManager]を制御。Timestepsは代替0.04か0.08くらい。 ただし、シミュレーションゲームでは安易に下げるのは危険

Rigidbodyの付いたGameObjectを操作するときの注意

  • SetPositionAndRotationを利用してはいけない。計算コストが高くつく
  • RigidBodyのMovePositionとMoveRotationを利用する。

Memory time

IL2CPP Memory Profiler

  • IL2CPPのツールでメモリの展開MAPとその詳細が視覚的に見られる。
  • UnityEngine.ObjectsとC# Object のメモリをみられる。
  • タイルのように使用されているメモリの大きさがわかる
  • タイルをクリックすることで詳細がみられる。
  • Textureのメモリ使用状況もわかるので、重要でないTextureの削減を考えることができる。

hideFlags

  • hideanddontsaveがついていると、そのメモリはアンロードされなくなる。
  • memoryleakを簡単に作り出せる便利なフラグ

Texutres non-readble

  • Textureを読み込むときに注意が必要
  • readbleで読み込んでしまうと、Memoryを2倍使ってしまう。
  • Textureをコードなどで変更する必要がないときはnon-readbleにする。
  • Texture2Dなどで変更した際もapply時に、non-readbleにするとよい

レガシーコード改善を読んで その2

本投稿の話題

既存コードを変更する際の技。今後、テストを導入できるようにするための布石となる方法。 Swiftに適応するならどうするか。

スプラウトメソッド

  • 既存のメソッド内に、新たなメソッドを呼び出すように変更を加える方法。
  • 新たなメソッド内だけでもテストが可能となる。

例えば、以下のようなコードがあったとする。

func example() {
   // doSomething 何かしらの処理を実行している
}

doSomethingの後に、何かしらの新たな処理を実行したい場合

func example() {
   // doSomething
   // example()内で処理を書くことも可能だが、以下のようにdoSomething2を別に用意する。
   doSomething2()
}

func doSomething2() {
   // 新たな処理をここに書く
}

上記のようにすることで、なるべく既存のdoSomethingに変更を加えずに、修正をすることができる。

また、もしdoSomethingが所属するクラスの依存関係が複雑すぎる場合でも、doSomething2の処理に必要な情報を引数とすることで、doSomething2だけでもテストが可能となる。(新規追加処理がテストできる)

スプライトクラス

  • 既存クラスに対して新たな責務を追加したい場合に有効
  • 新たな責務を別のクラスとして切り分けて置く
  • 変更が必要な既存処理で新たなクラスのインスタンスを生成/利用する。

コードが複雑になるため、使い過ぎには注意が必要となる。一方で別クラスに分けることで、新たな責務を担うクラスだけでもテストが可能となる。

ラップメソッド

  • 既存のメソッドをラップするメソッドを用意し、既存処理→新規処理(新規処理→既存処理)と呼び出す
  • 既存のメソッドの処理を一切触れることなく、新たな処理を追加することができる
  • 既存のメソッドの前・後処理として、処理が独立しているときに有効

例えば、以下のようなコードがあったとする。

func exmaple() {
   // doSomething
}

exmapleを呼び出している箇所すべてに対して、後処理(doSomething3)を追加したい場合

func exmaple(){
   doSomething2()
   doSomething3()
}

func doSomething2() {
   // doSomething
}

func doSomething3() {
   // 新たな処理
}

既存の処理をdoSomething2として再定義し、exmaple()では既存の処理と新たな処理(doSomething3)を呼びだす。

上記のようにすることで、既存の処理を変更せずに新たな処理を追加することができる。

新たな処理を一部だけ追加したい場合は、exmapleはそのままで、exmaple2を新たに定義し、exampleとdoSomething3を呼び出す。新たな処理を追加したい箇所だけexample2に変更すればよい。

ラップクラス

既存のクラスのメソッドをProtocolで抽出し、新たに定義するクラスをProtocolに適合させる。 新たなクラスのインスタンス生成時に、既存クラスのインスタンスを渡す。変更が必要な個所だけ修正する。 変更が必要ない箇所は既存クラスのインスタンスに処理を委譲する。

Protocolに抽出しなくても、既存クラスが継承可能ならば継承でも実現できる。

protocol SomeProtocol {
    func something1()
    func something2()
}

class SomeClass: SomeProtocol {
    func something1() {
        
    }
    
    func something2() {
        
    }
}

class SomeClassWrap: SomeProtocol {
    let someProtocol: SomeProtocol
    
    init(with something: SomeProtocol) {
        self.someProtocol = something
    }
    
    func something1() {
        self.someProtocol.something1()
    }
    
    func something2() {
        self.someProtocol.something2()
    }
}

レガシーコード改善ガイド (Object Oriented SELECTION)

レガシーコード改善ガイド (Object Oriented SELECTION)