GCC が提供する Multiply-Accumulate 組み込み関数の使用方法は?
float32x4_t vmlaq_f32 (float32x4_t , float32x4_t , float32x4_t);
この関数に渡さなければならない 3 つのパラメーターを誰か説明できますか? ソースレジスタとデスティネーションレジスタ、および関数が返すものを意味しますか?
ヘルプ!!!
GCC が提供する Multiply-Accumulate 組み込み関数の使用方法は?
float32x4_t vmlaq_f32 (float32x4_t , float32x4_t , float32x4_t);
この関数に渡さなければならない 3 つのパラメーターを誰か説明できますか? ソースレジスタとデスティネーションレジスタ、および関数が返すものを意味しますか?
ヘルプ!!!
簡単に言うと、vmla 命令は次のことを行います。
struct
{
float val[4];
} float32x4_t
float32x4_t vmla (float32x4_t a, float32x4_t b, float32x4_t c)
{
float32x4 result;
for (int i=0; i<4; i++)
{
result.val[i] = b.val[i]*c.val[i]+a.val[i];
}
return result;
}
そして、これらすべてが単一のアセンブラ命令にコンパイルされます:-)
この NEON アセンブラ組み込み関数は、次のような 3D グラフィックスの典型的な 4x4 行列乗算で使用できます。
float32x4_t transform (float32x4_t * matrix, float32x4_t vector)
{
/* in a perfect world this code would compile into just four instructions */
float32x4_t result;
result = vml (matrix[0], vector);
result = vmla (result, matrix[1], vector);
result = vmla (result, matrix[2], vector);
result = vmla (result, matrix[3], vector);
return result;
}
乗算後に結果を追加する必要がないため、これにより数サイクルが節約されます。足し算は非常に頻繁に使用されるため、最近では積和 hsa が主流になっています (x86 でさえ、最近の SSE 命令セットでそれらを追加しました)。
また、言及する価値があります。このような積和演算は、線形代数および DSP (デジタル信号処理) アプリケーションでは非常に一般的です。ARM は非常にスマートで、Cortex-A8 NEON-Core 内に高速パスを実装しました。この高速パスは、VMLA 命令の最初の引数 (アキュムレータ) が先行する VML または VMLA 命令の結果である場合に有効になります。詳しく説明することもできますが、一言で言えば、このような一連の命令は、VML / VADD / VML / VADD シリーズよりも 4 倍速く実行されます。
私の単純な行列乗算を見てください: 私はまさにそれをしました. この高速パスにより、VMLA の代わりに VML と ADD を使用して記述された実装よりも約 4 倍速く実行されます。
を Google で検索すると、RVCT コンパイラ ツールのリファレンスvmlaq_f32
が見つかりました。これがそれが言うことです:
Vector multiply accumulate: vmla -> Vr[i] := Va[i] + Vb[i] * Vc[i]
...
float32x4_t vmlaq_f32 (float32x4_t a, float32x4_t b, float32x4_t c);
と
次の型は、ベクトルを表すために定義されています。NEON ベクトル データ型は、次のパターンに従って名前が付けられます。 <type><size>x<number of lanes>_t たとえば、int16x4_t は、それぞれが符号付き 16 ビット整数を含む 4 つのレーンを含むベクトルです。表 E.1 にベクトルのデータ型を示します。
IOW、関数からの戻り値は 4 つの 32 ビット float を含むベクトルになり、ベクトルの各要素は と の対応する要素を乗算しb
、c
の内容を加算することによって計算されa
ます。
HTH
result = vml (matrix[0], vector);
result = vmla (result, matrix[1], vector);
result = vmla (result, matrix[2], vector);
result = vmla (result, matrix[3], vector);
ただし、このシーケンスは機能しません。問題は、x コンポーネントが行列の行によって変調された x のみを累積し、次のように表現できることです。
result.x = vector.x * (matrix[0][0] + matrix[1][0] + matrix[2][0] + matrix[3][0]);
...
正しい順序は次のとおりです。
result = vml (matrix[0], vector.xxxx);
result = vmla(result, matrix[1], vector.yyyy);
...
NEON と SSE には、フィールドの組み込み選択機能がありません (これには、ベクトル レジスタごとに命令インコーディングで 8 ビットが必要になります)。たとえば、GLSL/HLSL にはこの種の機能があるため、ほとんどの GPU にもあります。
これを達成する別の方法は次のとおりです。
result.x = dp4(vector, matrix[0]);
result.y = dp4(vector, matrix[1]);
... // もちろん、行列は転置されて同じ結果が得られます
mul,madd,madd,madd シーケンスは、ターゲット レジスタ フィールドの書き込みマスクを必要としないため、通常は優先されます。
それ以外の場合、コードは見栄えがします。=)