はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】Burst Direct CallでXor Shiftを活用したノイズを作ろうとしたら失敗した話

はじめに

今回はBurst Direct Callを触って、簡単な計測をしてみたという記事になります。

www.youtube.com

公式のYoutube動画でBurst Direct Callの紹介動画があったので、自分でも触ってみようかなと思った次第です。

結果としてあんまり良い結果が得られなかったのですが、備忘録として残しておきます。

概要

Burst 1.5 の新機能のうち特筆するべきものに、Direct Call(直接呼び出し)と呼ばれるものがあります。Burst では、HPC#コンパイラを使って Unity のジョブシステム上で実行されるタスクを高速化するために、まずジョブに注目しました。その後、関数ポインターを追加し、Burst のコードの一部をどこからでも管理・呼び出しできるようにしました。

2021 年は Burst 1.5 でコードを高速化しよう | Unity Blog

今まではBurst & Job Systemの組み合わせで利用していたのですが、Burst Direct Callを利用すればC# Job Systemを使わずにBurstの恩恵を受けることができます。

ただUnity 2019.4 or laterでないとサポートされていないよう。

Burst supports the ability to call out to Burst compiled methods directly from managed code (2019.4+ only).

Language Support | Burst | 1.6.6


あとBurst v1.5まではMain Threadからしか呼び出せませんでしたが、Burst v1.6からはどのスレッドでも大丈夫だそうです。

Fixed the 1.5 restriction that Direct Call methods can only be called from the main thread, now they work when called from any thread.

Changelog | Burst | 1.6.6

環境

MacBook Pro (16-inch, 2019)
Unity2021.3.0f1
Burst v1.6.6

インストール

PackageManagerUnity Registryからインストールできます。

Busrtのインストール

XorShift

今回は適当な乱数を生成して2048x2048の画像に適応するサンプルを作成していきます。

擬似乱数を生成するにはLCGやメルセンヌ・ツイスタ, XorShift, PCGなど色々とアルゴリズムがあるそうですが今回はXor Shiftを使ってみたいと思います。
ja.wikipedia.org

private uint value = 2463534242;

/// <summary>
/// 32bitのXorShift
/// </summary>
private uint XorShift()
{
    value ^= value << 13;
    value ^= value >> 17;
    value ^= value << 5;
    return value;
}

またUnity.Mathematics.Randomというランダム用の構造体があるのですが、乱数生成の仕組みを知りたかったのもあり自作してみました。
(UnityEngine.Randomはメインスレッド以外で動作しなかったり、マネージドピープを利用していたりと辛いところがある)

実装

Classstatic関数に[BurstCompile]をつけます。
Language Support | Burst | 1.6.6

[BurstCompile]
public sealed class BurstDirectCallTest : MonoBehaviour
{
    private const int Size = 2048;
    [SerializeField] private RawImage image;

    private Texture2D _texture;
    private uint _xorShiftValue = 2463534242;

    private void Start()
    {
        // アルファのみのテクスチャを用意
        _texture = new Texture2D(Size, Size, TextureFormat.Alpha8, false);
        image.texture = _texture;
    }

    private void Update()
    {
        // マネージドヒープではなく、ネイティブメモリで確保された配列を用意
        var nativeArray = new NativeArray<byte>(Size * Size, Allocator.Temp);

        // ランダムなノイズを生成
        for (var x = 0; x < Size; x++) 
            for (var y = 0; y < Size; y++)
                nativeArray[x * Size + y] = GetPixel();

        // 画像にピクセルを反映, GPUにアップロード
        _texture.LoadRawTextureData(nativeArray);
        _texture.Apply();

        // GCの管理外なので、きっちり破棄するのを忘れず
        nativeArray.Dispose();
    }

    private void OnDestroy()
    {
        image.texture = null;
        Destroy(_texture);
    }

    /// <summary>
    /// ピクセルの色を取得する
    /// </summary>
    private byte GetPixel()
    {
        // 0 ~ 255(alpha : 8bits)の擬似乱数を返す
        _xorShiftValue = XorShift(_xorShiftValue);
        return (byte) (_xorShiftValue % 255);
    }

    /// <summary>
    /// 32bitのXorShift
    /// </summary>
    [BurstCompile]
    private static uint XorShift(uint value)
    {
        value ^= value << 13;
        value ^= value >> 17;
        value ^= value << 5;
        return value;
    }
}

このコードをみるとNativeArrayをキャッシュして使い回した方がいいんじゃ?みたいな感覚を抱きますが、これってマネージドヒープの考え方って事でいいんですかね。

Unity.Collections.Allocator.Tempがあるくらいですし、間違ってはいないとは思うのですが。

エディタ上での結果

試してみた結果以下のようになりました。またMonoと表記されているものは[BurstCompile]を取り除いた以外は同じコードを使ってます。

←Mono Burst Direct Call→
  • Safety Checks : Off
  • Enable Compilation : On
  • Synchronous Compilation : On
  • Native Debug Mode Compilation : Off
  • Show Timings : Off

Quick Start | Burst | 1.6.6

ちゃんと平均FPSを測るべきではありますが、ざっと見た感じ以下ぐらいになっていました。

種類 FPS
Mono 19.1
Burst Direct Call 6.1

まさかのMonoの方が早いという結果に。

エディター上で動かしたせいなのか、コードが間違っているのか、設定が間違っているのか。

一応コードはkeijiro神とほぼ同じにしたつもりなのですが、もう何も分かりません。

アセンブリが読めたりできれば一番良かったのですが......。

StandaloneOSXで実験

種類 FPS
Mono 28.0
Burst Direct Call 10.5

※ 計測値は正確な値ではなく、ざっと見積もった値です。

さいごに

動画ではMemoryMarshal.CastSpanを生成(ポインタと長さ)&渡すという手法を高速化のために紹介されていましたが、それをやる前でも結構な速度改善になっていたんですよね。

別にエディタだから失敗したみたいでもなさそうですし、暇があればもっと細かく調べてみたいと思います。