テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unit】NativeListをIJobParallelForで使いたい

今回はNativeListIJobParallelForを使用して利用してみます。

NativeListをIJobParallelForで使う

NativeListは要素を増減出来るという点で言えば便利な機能です。ただしIJobを使用している場合には大体の機能を使えるのですがIJobParallelForで書込を行おうとすると、InvalidOperationException: The previously scheduled job Job名 reads from the NativeArray NativeList名. You must call JobHandle.Complete() のようなエラーが表示されて実行できません。

これを回避するには、NativeList.AsDeferredJobArray()を使用して、NativeListをNativeArrayとして扱います。これはコピーではないので、NativeArrayの変更はNativeListにも反映されます。

なお[ReadOnly]を使用している場合には、こんな事をせずともListのまま使用できます。また、NativeArrayに変換しているためAddは出来ません。
また、Jobのパッケージが必要です。

f:id:tsubaki_t1:20190224221935j:plain
JobパッケージのIJobParallelForDeferExtensionsが必要です

使い方(NativeListの用意)

まずNativeListの用意をします。NativeListのAddは並列処理出来ないので、そこは諦めます。

ジョブ側

struct AddJob : IJob
{
    public NativeQueue<int> queue;
    public NativeList<int> list;

    public void Execute()
    {
        while (queue.TryDequeue(out int item))
        {
            if (item % 2 == 0) // 2で割り切れる要素だけ追加
                list.Add(item);
        }
    }
}

呼び出す側

 // Listに登録する要素を
NativeQueue<int> queue = new NativeQueue<int>(Allocator.TempJob);
queue.Enqueue(3); queue.Enqueue(2);
queue.Enqueue(4); queue.Enqueue(5);

NativeList<int> list = new NativeList<int>(8, Allocator.TempJob);

// 内容の更新
var handle = new AddJob { queue = queue, list = list }.Schedule();

使い方(NativeListの内容を更新)

次にNativeListIJobParallelForで更新します。更新する際には上に書いたとおりAsDeferredJobArrayを使用します。特に指定せずとも自動で変換されるっぽいですが、精神の安全のために使用しておきます。

まずジョブですが、普通にNativeArrayを使用します。ここにNativeListの要素が格納されます。

struct UpdateJob : IJobParallelFor
{
    public NativeArray<int> array;

    public void Execute(int index)
    {
        array[index] += 1;
    }
}

使用する側は少しだけ特殊です。まずNativeListをNativeArrayに変換しているのが一点、そしてジョブの長さにNativeListを設定する点です。

後者は普通にNativeListの長さがジョブ発行時にはわからないので、そういった場合の対策といった感じでしょうか。

    handle = new UpdateJob {
        array = list.AsDeferredJobArray()   // AsDeferredJobArrayでNativeArrayにする
    }.Schedule(list, 4, handle);            // 要素数の部分にはlistを登録

これでListの中身を並列処理で書き換えました。

中身を確認する

最後に中身を確認します。こちらは普通に[Readonly]な NativeListで問題ありません。

struct ShowLogJob : IJobParallelFor
{
    [ReadOnly] public NativeList<int> list;

    public void Execute(int index)
    {
        Debug.Log(list[index]);
    }
}

使う側も概ね同じです。

handle = new ShowLogJob
{
    list = list
}.Schedule(list, 8, handle);

実行結果

処理の内容はコードは、

  1. 3/2/4/5をQueueに追加
  2. 2で割り切れる数(2/4)をListに追加
  3. Listの要素に1を追加(2/4 が 3/5になる)
  4. Listの中身をDebugLogで表示

という内容で、実際確認してみると、ちゃんと期待通りの数字が出ています。

f:id:tsubaki_t1:20190224223223j:plain

コード全文(を少し改造したもの)

gist.github.com

感想

NativeListで並列処理出来ないかと色々と確認した結果、最終的にコレになりました。Addが出来ないのは残念ですが、まぁ。

関連

tsubakit1.hateblo.jp