おれんじりりぃぶろぐ

きっと何者にもなれないエンジニアのブログ

Googleドキュメントにソースコードを綺麗に挿入する

いくつか試しましたが、「Code Block for Google Docs」というアドオンが良かったです。

[アドオン] -> [アドオンを取得] -> 「Code Block for Google Docs」で検索して追加できます。
使い方は、[アドオン] -> [CodeBlocks] -> [Start]から整形することができます。
コードを選択して[Format]を押すと整形されます。

こんな感じで表示されます。 f:id:orange_lily27:20180518203417p:plain

UnityでGCの回数を計測する

GC.CollectionCount()を使ってGCの回数を取得することができます。 GC.CollectionCount(0)はプロセスを起動してからのGC回数を返してくれます。

int startGcCount = GC.CollectionCount(0);

// GCが走りそうな処理

Debug.Log($"GC回数: {GC.CollectionCount(0) - startGcCount}回");

「幻夏」を読んでの感想

まえおき

GWに読んだ幻夏の感想です。

幻夏 (角川文庫)

幻夏 (角川文庫)

感想

現在、一人の女の子が誘拐された。過去に、一人の男の子が突然姿を消した。一つのマークを手がかりにつながる2つの事件の謎が解き明かされていく。さらに、事件の真相とともに複雑な人間関係と心情が紐解かれクリアになっていくノスタルジー型のミステリー小説。

冷静に考えると無理がある場面も存在するが、紙の上ではあまり気にならなかったです。とても日本的なミステリー作品で、人間関係が複雑に絡んだ本当の悪がはっきりしない作品です。事件の解決手法は、過去を懐古しながら事件の真相、犯人の動機を見出していく方法です。 この作品の読了後は、オレンジのぼんやりした光の中から、細い黒い糸を手繰って手繰って抜き切るような気持ちよさを感じることできました。

おすすめ

★★★☆☆

特にシリーズものでもないので単刊でサクッと読めておすすめです。

JenkinsでHockeyAppへのアップロードを下流Jobにする

まえおき

JenkinsでHockeyAppへのバイナリアップロードを別ジョブとして切り離す方法です。
Jobを切り離さない場合、ジョブが失敗した時にビルド自体が失敗したのかアップロードが失敗したのか分かりづらい、さらにビルド時間とアップロードにかかる時間が一即多になってしまい純粋なビルド時間が分かりづらいといったことが起こってしまいます。

ビルドJob(上流Job) ---> HockeyAppへのアップロードJob(下流Job)

Jobを切り離すことで以下のメリットが期待できます

  • Job失敗原因が特定しやすい
  • Jobの実行時間が分かりやすくなる

やり方

まずは上流のビルドJobを作成します。 上流Jobでは成果物の保存を行います。

f:id:orange_lily27:20180505211103p:plain

[ビルド後の処理追加] -> [成果物の保存]を追加する

次にHockeyAppアップロード用の下流Jobを作成します。 f:id:orange_lily27:20180505211739p:plain

「ビルド・トリガ」で「他プロジェクトの後にビルド」を設定します。

次に上流Jobでの成果物をコピーします。 「CopyArtifactPlugin」を導入します。

[ビルド手順の追加]で[他プロジェクトから成果物をコピー]を選択して以下のように設定します。 f:id:orange_lily27:20180505212153p:plain

あとはビルド後の処理でHockeyAppへのアップロードをします。 HockeyAppへのアップロードはHockeyAppPluginを導入することで簡単にできます。

f:id:orange_lily27:20180506003851p:plain

以上でビルドJobとHockeyAppへのアップロードのJobを切り離して実行することができます。

UnityでSwitchのJoyConを使う

まえおき

switchのJoyConをUnityで使う方法です。 基本的には以下の記事を参考にしましたが、一部ハマった箇所があるのでメモしておきます。

http://baba-s.hatenablog.com/entry/2017

環境

  • Mac
  • Unity2018.1.b13

やり方

JoyConとMacBluetoothで接続します。

f:id:orange_lily27:20180428205243p:plain

右と左で別々のデバイスとして認識されます。

Edit -> ProjectSettings -> Inputで以下の設定します。 f:id:orange_lily27:20180428205031p:plain

f:id:orange_lily27:20180428205042p:plain

手元の環境では参考記事とAxisの番号が異なっていて、嵌ってしまいました。 こちらのアセットで簡単に確認することが出来ました。

Controller Tester - Asset Store

検出は以下できます。

float h1 = Input.GetAxis("Horizontal1");

Unityのマルチスレッド界隈のはなし

Unityでのマルチスレッド界隈について断片化した理解にとどまっていたので、自分なりのまとめを行ってみました。

まず非同期実装と並列プログラミングは分けて考えよう

  • 非同期実装は順番通りに処理を実行しない、逐次処理とは対比的な処理のことである
    • 非同期処理は通信などの重い処理を別スレッドに逃しメインスレッドを専有させないことを目的としている
  • 一方で、並列実装は比較的短い(1フレーム内で収まる)処理を複数のスレッド使って同時に実行することでマルチコアな物理ハードウェアのパフォーマンスを最大限に引き出そうというアプローチである
  • 非同期実装に適しているのがawait/async、Taskを使った方法である
  • 並列プログラミングの実装に適しているのがC#のParallelクラス、Unityが提供するC#JobSystemになる

C#JobSystem

  • 元々、UnityエンジンではWorkerThreadをコア数-1(例外あり)で持っていた、それをユーザーにも開放した形になる
  • 1フレームで収まる処理を並行的に行うのに適している
    • 通信やI/O待ちが発生する非同期的な処理には向かない
      • その場合には、後述するawait、Taskを使っての実装を行うのがよい
  • イテレーションを回すコレクションはC#のコレクションではなくUnityが独自に用意したNativeArrayというunsafeなコレクションになる
    • NativeArray以外にもNativeListなど順次実装されていくとのこと
    • C++の配列のように必要な領域をあらかじめ確保して必要なくなったら開放をしてあげる必要がある
      • そのため下手な使い方をするとメモリーリークを起こす
        • 一応デバック方法はあって、Editor上だとメモリリークを起こしているという警告が出る
  • C#のコレクションはメモリ上に離散的に存在するのでキャッシュヒット率がとても悪い、JobSystemではNativetiveArrayを使用することでメモリ上に連続的に展開を行いキャッシュヒット率を上げる
  • そのためintやstructなどのプリミティブ型に限られる
  • メインスレッド制約のあるUnityの通常のAPIは使えない
    • MainThreadで実行してくださいというエラーが出る
  • JobSystem上で動く専用のAPIが順次増えていく予定とのこと
    • 今のところ(2018.1.b13)専用のAPIが用意されできることは以下の3つである
      • NavMesh
      • RayCast
      • Transformの書換え
        • 逆に上記に上げる3つ以外のことは制約や煩雑さがあるため使わないほうが良いかもしれない、簡潔な記述ができるC#のParallelクラス(後述)を使ったほうがいいのではないだろうか
  • JobSystem内で戻り値を返す方法がやや煩雑な印象である
  • 詳しい使い方はこちらのブログ記事を参考にすると良いと思います

C#での非同期実装

  • C#5でasync/awaitというキーワードが導入された
  • awaitは文字通り「待つ」の仕組みを提供するものである
var task = SampleAsync(); // 実行
Debug.Log("Immediate Execution"); <- 即時実行される
await task;
Debug.Log("SampleAsync() compleated!"); SampleAsync()の実行完了を待って実行される
  • awaitにTaskクラスを組み合わせて使うことで 非同期実装が容易に実現できる
  • Taskは一連の処理をまとめるためのものである。1つのTaskをメインではない別スレッドで丸っと実行させるということも簡単に実現できる
  • awaitは特定の条件を満たしたクラスに対して使うことができ、Taskがその条件を満たした実装になっているので使用が可能となっている
  • 条件を満たすように実装を行えばawaitをTask以外にも利用でき、例えばUnityコルーチンをawaitさせるということも可能である
  • WebHttpRequestなどは非同期実装が行いやすいようにTaskクラスを返してくれる非同期の通信メソッドがあらかじめ用意されている
var webReq = (HttpWebRequest)WebRequest.Create(URL);
await webReq.GetResponseAsync();

C#Parallelクラス

  • 並行プログラミングを簡単に実装できる仕組みである
  • 立ち位置的にはC#Jobsystemとオーバーラップする
  • UnityではC#JobSystemのほうがパフォーマンスは出るのでしょうが、簡単に記述できるという利点がこちらにはある
Parallel.For(0, N, i =>
{
    Debug.Log(i); // 複数のスレッドで同時並行的に実行する
});

参考

ここで書いたC#JobSystemの情報については、こちらの勉強会に参加して得た情報になります。

Unityコルーチンをawaitするサンプル

概略

C#5からawait句が導入され非同期実装がとてもお手軽にできるようになりました。 awaitはTaskへ使うものと思ってしまいがちですが、一定の条件を満たせばawaitを使った非同期実装を行うことが出来ます。

実装

AwaitHelloWorld.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;

public class AwaitHelloWorld : MonoBehaviour
{

    [SerializeField] private GameObject go;
    private ControlCoroutine controlCoroutine;
    
    private async void Start()
    {
        int starttime = DateTime.Now.Hour * 60 *60 * 1000 + DateTime.Now.Minute * 60 * 1000 + 
                    DateTime.Now.Second * 1000 + DateTime.Now.Millisecond;
        
        controlCoroutine = go.GetComponent<ControlCoroutine>();
        var result = new HelloWorldAwaitable(controlCoroutine);
        
        /*for (var i = 0; i < 1000; i++)
        {
            Debug.Log("HelloWorld");
        }*/
        
        int now = DateTime.Now.Hour * 60 *60 * 1000 + DateTime.Now.Minute * 60 * 1000 + 
                  DateTime.Now.Second * 1000 + DateTime.Now.Millisecond;
        Debug.Log($"time:{now-starttime}");

        await result;
        Debug.Log(result);
    }

    public void ClickButton()
    {
        Debug.Log("On Clicked Button!!!");
    }
}

public class HelloWorldAwaitable
{
    private ControlCoroutine cc;
    public HelloWorldAwaitable(ControlCoroutine controlCoroutine)
    {
        this.cc = controlCoroutine;
    }
    
    public HelloWorldAwaiter GetAwaiter()
    {
        return new HelloWorldAwaiter(cc);
    }
}

public struct HelloWorldAwaiter : INotifyCompletion
{
    private ControlCoroutine cc;

    public HelloWorldAwaiter(ControlCoroutine controlCoroutine)
    {
        this.cc = controlCoroutine;
    }
    
    public bool IsCompleted
    {
        get { return false; }
    }

    public string GetResult()
    {
        return "OnCompleated!";
    }

    public void OnCompleted(Action continuation)
    {
        cc.RegisterCoroutine(() =>
        {
            continuation();
        });
        
    }
}

ControlCoroutine.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ControlCoroutine : MonoBehaviour 
{
    public void RegisterCoroutine(Action action)
    {
        StartCoroutine(WorkCoroutine(action));
    }

    private IEnumerator WorkCoroutine(Action action)
    {
        for (var i = 0; i < 1000; i++)
        {
            Debug.Log("HelloWorld");
            yield return null;
        }
        // callbackを実行する
        action();
    }
}

それぞれのソースコードをGameObjectにComponentとして追加します。

まとめ

けっこうコード量が増えパット見は面倒な感じになってしまいました。 しかしWorkCoroutineのところだけ、カスタマイズしてあとは共通のものと考えてしまえばそれほど冗長でもなさそうです。 コルーチンを連続して使うときなどはコールバック地獄からも開放されそうです。

参考

Unity2018での文字列の扱い方

概略

Unityで文字列の連結を行う方法として以下の3つのパフォーマンスを比較してみました。1000回ループし文字列を連結させた時の処理時間とGC回数を計測しました。

ソースコード

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public class LinkStringSample : MonoBehaviour
{
    private const int N = 10000;

    public void StingTest()
    {
        
        int starttime = DateTime.Now.Hour * 60 *60 * 1000 + DateTime.Now.Minute * 60 * 1000 + 
                        DateTime.Now.Second * 1000 + DateTime.Now.Millisecond;
        string str = string.Empty;

        for (int i = 0; i < N; i++)
        {
            str += "aaa";
        }
        
        int now = DateTime.Now.Hour * 60 * 60 * 1000 + DateTime.Now.Minute * 60 * 1000 + 
                  DateTime.Now.Second * 1000 + DateTime.Now.Millisecond;

        Debug.Log($"StingTest()実行時間: {now - starttime}ms");
        // GC.CollectionCountはプロスを起動してからのGC回数を返す
        // https://msdn.microsoft.com/ja-jp/library/system.gc.collectioncount(v=vs.110).aspx
        Debug.Log($"GC回数: {GC.CollectionCount(0)}回");

    }

    public void StringBuilderTest()
    {
        int starttime = DateTime.Now.Hour * 60 *60 * 1000 + DateTime.Now.Minute * 60 * 1000 + 
                        DateTime.Now.Second * 1000 + DateTime.Now.Millisecond;
        
        StringBuilder strBuilder = new StringBuilder();

        for (int i = 0; i < N; i++)
        {
            strBuilder.Append("aaa");
        }
        
        int now = DateTime.Now.Hour * 60 * 60 * 1000 + DateTime.Now.Minute * 60 * 1000 + 
                  DateTime.Now.Second * 1000 + DateTime.Now.Millisecond;
        
        Debug.Log($"StringBuilderTest()実行時間: {now - starttime}ms");
        Debug.Log($"GC回数: {GC.CollectionCount(0)}回");
    }

    public void BuildInStringTest()
    {
        int starttime = DateTime.Now.Hour * 60 *60 * 1000 + DateTime.Now.Minute * 60 * 1000 + 
                        DateTime.Now.Second * 1000 + DateTime.Now.Millisecond;
        
        string str = String.Empty;

        for (int i = 0; i < N; i++)
        {
            str = $"{str}aaa";
        }
        
        int now = DateTime.Now.Hour * 60 * 60 * 1000 + DateTime.Now.Minute * 60 * 1000 + 
                  DateTime.Now.Second * 1000 + DateTime.Now.Millisecond;
        
        Debug.Log($"BuildInStringTest()実行時間: {now - starttime}ms");
        Debug.Log($"GC回数: {GC.CollectionCount(0)}回");
    }
}

検証環境

  • Unity2018.3b13
    • .NET4.x
    • il2cpp
  • ios
    • iPhone7Plus
  • android
    • s8

結果

StingTest StringBuilderTest BuildInStringTest
Editor 511/168 0.6/56 1248/288
ios 234/1577 0.6/6 542/3885
android 708/2015 1.0/6 1613/5656
  • 処理時間(ミリsec)/GC回数(回)
  • 5回の平均値
  • EditorのGC回数は参考値

まとめ

  • 文字列の連結には基本的にはStringBuilderを使うのがよい
  • 特にインライン形式での埋め込みは高コストなのでDebugログなどの限定的な使用に留めるべきである

参考