ループのベクトル化
Burst は、ループのベクトル化 を使用してコードのパフォーマンスを向上させます。この手法を使用すると、一度に 1 つの値ではなく、同時に複数の値がループ処理されるため、コードのパフォーマンスが加速します。以下はその例です。
[MethodImpl(MethodImplOptions.NoInlining)]
private static unsafe void Bar([NoAlias] int* a, [NoAlias] int* b, int count)
{
for (var i = 0; i < count; i++)
{
a[i] += b[i];
}
}
public static unsafe void Foo(int count)
{
var a = stackalloc int[count];
var b = stackalloc int[count];
Bar(a, b, count);
}
Burst は Bar
内のスカラーループをベクトル化したループに変換します。その後、一度に 1 つの値をループ処理する代わりに、同時に複数の値をループ処理するコードを生成します。これにより、より高速なコードが生成されます。
以下は、上記 Bar
内のループの AVX2
用に Burst が生成する x64
アセンブリです。
.LBB1_4:
vmovdqu ymm0, ymmword ptr [rdx + 4*rax]
vmovdqu ymm1, ymmword ptr [rdx + 4*rax + 32]
vmovdqu ymm2, ymmword ptr [rdx + 4*rax + 64]
vmovdqu ymm3, ymmword ptr [rdx + 4*rax + 96]
vpaddd ymm0, ymm0, ymmword ptr [rcx + 4*rax]
vpaddd ymm1, ymm1, ymmword ptr [rcx + 4*rax + 32]
vpaddd ymm2, ymm2, ymmword ptr [rcx + 4*rax + 64]
vpaddd ymm3, ymm3, ymmword ptr [rcx + 4*rax + 96]
vmovdqu ymmword ptr [rcx + 4*rax], ymm0
vmovdqu ymmword ptr [rcx + 4*rax + 32], ymm1
vmovdqu ymmword ptr [rcx + 4*rax + 64], ymm2
vmovdqu ymmword ptr [rcx + 4*rax + 96], ymm3
add rax, 32
cmp r8, rax
jne .LBB1_4
Burst はループを展開して 4 つの vpaddd
命令にベクトル化しました。この命令では、ループを繰り返すごとに 8 回、合計 32 回の整数加算が行われます。
ループベクトル化 intrinsic
Burst には、ループベクトル化の想定を表す実験的な intrinsic として Loop.ExpectVectorized
と Loop.ExpectNotVectorized
が含まれています。これによって Burst はコンパイル時にループベクトル化を検証します。これは自動ベクトル化を中断するような状況で役に立ちます。例えば、以下のようにコードに分岐を導入するような場合です。
[MethodImpl(MethodImplOptions.NoInlining)]
private static unsafe void Bar([NoAlias] int* a, [NoAlias] int* b, int count)
{
for (var i = 0; i < count; i++)
{
if (a[i] > b[i])
{
break;
}
a[i] += b[i];
}
}
これによってアセンブリは以下のように変更されます。
.LBB1_3:
mov r9d, dword ptr [rcx + 4*r10]
mov eax, dword ptr [rdx + 4*r10]
cmp r9d, eax
jg .LBB1_4
add eax, r9d
mov dword ptr [rcx + 4*r10], eax
inc r10
cmp r8, r10
jne .LBB1_3
ループがスカラーであり、ループを繰り返すごとに 1 回の整数加算しかしないので、これは最適とは言えません。コード内のどこで行われているか特定しにくい可能性があるため、ループベクトル化の想定を表す実験的な intrinsic である Loop.ExpectVectorized
と Loop.ExpectNotVectorized
を使用します。これによって Burst はコンパイル時にループベクトル化を検証します。
この intrinsic は実験的なものなので、有効にするには UNITY_BURST_EXPERIMENTAL_LOOP_INTRINSICS
プリプロセッサ定義を使用する必要があります。
以下の例は、元の Bar
の例に Loop.ExpectVectorized
intrinsic を使用したものを示しています。
[MethodImpl(MethodImplOptions.NoInlining)]
private static unsafe void Bar([NoAlias] int* a, [NoAlias] int* b, int count)
{
for (var i = 0; i < count; i++)
{
Unity.Burst.CompilerServices.Loop.ExpectVectorized();
a[i] += b[i];
}
}
これで Burst はループがベクトル化されているかどうかをコンパイル時に検証します。ループがベクトル化されていない場合は、Burst でコンパイラーエラーが発生します。以下の例ではエラーが発生します。
[MethodImpl(MethodImplOptions.NoInlining)]
private static unsafe void Bar([NoAlias] int* a, [NoAlias] int* b, int count)
{
for (var i = 0; i < count; i++)
{
Unity.Burst.CompilerServices.Loop.ExpectVectorized();
if (a[i] > b[i])
{
break;
}
a[i] += b[i];
}
}
Burst ではコンパイル時に以下のエラーが発生します。
LoopIntrinsics.cs(6,9): Burst error BC1321: The loop is not vectorized where it was expected that it is vectorized.
Important
これらの intrinsic は if
ステートメント内では動作しません。Burst でこの現象を防ぐことはできないため、これに関するコンパイル時のエラーは発生しません。