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

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

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)

UserDefaultsを初期化する

UserDefaults を初期化するためには

if let bundleId = Bundle.main.bundleIdentifier {
    UserDefaults.standard.removePersistentDomain(forName: bundleId)
}
  • persistentDomain(forName:)で domainNameで指定した、KeyとValueのディクショナリーが受け取れる。

  • domainNameをBundleIdentiferに指定することで、アプリ内すべてのKeyとValueが得られる

iOSで使いにくいUserDefaultをなんとかする

はじめに

iOSアプリにおいて、アプリを消しても値を保存しておける、UserDefaultはとても便利だと思う。 しかしながら、どうしても使いにくい部分が存在する。 例えば、UserDefaultの値を取り出すKeyがStringなところだ。

よくある使い方

// 保存する
UserDefaults.standard.set("sample", forKey: "sampleKey")
UserDefaults.standard.synchronize()

// とりだす
var str = UserDefaults.standard.string(forKey: "sampleKey")
  • Key値をタイポすると実行時まで築くことができない
  • stored propertyのように使用したい

stored propertyのように振る舞うために

前準備

class DefaultsKeys {
   static let sampleKey = DefaultsKey<String>("sampleKey")

   private init() {}
}

class DefaultsKey<ValueType>: DefaultsKeys {
   let key: String
   init(_ key: String) {
      self.key = key
   }
}

extension UserDefaults {
   subscript(key: DefaultsKey<String>) -> String {
      get {
         if let value = string(forKey: key.key) {
             return value
         } else {
             return ""
         }
      }
      set {
         set(newValue,forkey: key.key)
         synchronize()
      }
   }
}

使い方

UserDefaults.standard[.sampleKey] = "sample"
let str = UserDefaults.standard[.sampleKey]

その他

  • static let sampleKey のところを増やすことでKeyを増やせる
  • subscript を増やせばString以外にも対応可能
  • DefalutsKeyとDefaultsKeysを統合したかったのですが、staticプロパティはGenericタイプに対応していない。

おわりに

なんとかスッキリと利用することができるようになったので満足。

参考URL

github.com

qiita.com

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

クラスをテストにしやすくするには

 

1. クラス間の依存性を下げる。

2. 他のクラスを使用する場合、そのやりとりをプロトコルで定義することで、テスト用のモックと振替可能にする。

 

1. クラス間の依存性を下げる

クラスをテストする上で、大変になってくるのは、テスト対象のクラスを個別で生成•使用することが難しくなこと。

あるクラスが他のクラスを参照している場合、そのクラスをテストするためには他のクラスも生成しなければならない。他のクラスもまた別のクラスを参照するとかになると、どんどん話はややこしくなっていく。

では、どうするのか。

一番良いのはクラスが他のクラスに依存しなくても成立するようにすることだ。しかし、それが困難な場合もある。

その場合、必要に応じて他のクラスの情報を渡すのが良いだろう。インスタンス生成時のイニシャライザやメソッドの引数にその情報を渡すのだ。

引数と渡すとこで、テスト時にはその引数をテスト用クラスに差し替えることが可能となる。

 

2. 他のクラスとのやりとりをプロトコルで定義する。

1. の話の続きとして、1.では引数で他のクラスとの依存性注入すると書いた。

今度は、依存される側のクラスをテストクラスに差し替えることを考えよう。

依存される側とクラスをどうやって、テストクラスに差し替えるか。

そこで利用するのがプロトコルである。プロトコルで、依存する側が必要とする処理を定義しておき、依存される側がそのプロトコルに則って実装する。

こうすることで、依存される側をテストクラスに差し替える場合、プロトコルで定義されたメソッドのみをテスト用に書くだけで良い。

後は、イニシャライザやメソッドの引数としてテスト用クラスを渡すことで、テストを実施することができる。

 

追記(2017/4/22)

イニシャライザの引数にすべてのクラスを渡すのは困難。

渡されるクラスがないと、生成されるクラスが存在できない場合、依存が高い場合はイニシャライザの引数として渡す。

クラス生成後に状態のように後から別のクラスをSetする場合は、イニシャライザで渡したり、メンバ変数に参照を渡すよりも、setメソッドを定義したほうがよさそう。

テスト時もsetメソッドにダミーのテストを渡せるように。

メンバ変数を直接いじるは、カプセル化的にもよくない。

もちろん、setメソッドではプロトコルでsetできるようにしておくのがベスト。

 

 

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

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