NEONコプロセッサ

NEONコプロセッサの概要

Cortex-Aシリーズプロセッサに搭載されているSIMD演算(*1)を行う演算器で、マルチメディア演算(ビデオエンコード / デコード / 画像処理 / 音声処理など)や大量のデータを演算する場合、効率的に処理できます。

【演算可能なデータ形式一覧(*2)】
データ型 8ビット 16ビット 32ビット 64ビット
符号なし整数 U8 U16 U32 U64
符号付き整数 S8 S16 S32 S64
型指定なし整数 I8 I16 I32 I64
浮動小数点 F16 F32
多項式 P8 P16
  • (*1)SIMD(single instruction multiple data)演算とは、1命令で複数データの演算を行うコンピュータの並列化の形態のことをいいます(パック演算 / パックド演算・ベクトル演算とも表現します)。
  • (*2)命令によって使用できないデータ型がありますので注意ください。

ARMレジスタを使用した演算処理例

ARMレジスタの演算は、1命令で1演算処理を行います。

【ARMレジスタを使用した演算例】

図1

NEONレジスタを使用した演算処理例

NEONレジスタの演算処理は、指定したデータサイズにて1命令で複数の演算処理を行います。演算処理は、指定したデータサイズごとに行われます。64ビット幅のレジスタを選択し、16ビット幅の演算を行う場合、0から15ビット、16から31ビット、32から47ビット、48から63ビットの各々で演算を行い、この単位をレーンと呼びます。

【NEONレジスタを使用した演算例】

図2

NEONレジスタセット

64ビット幅の32個のレジスタ(d0からd31)または、128ビット幅の16個のレジスタ(q0からq15)として使用することができます。d0レジスタの内容を変更した場合、q0レジスタの下位64ビットが同一の内容になります。

図3

NEONコプロセッサの初期化処理

NEONコプロセッサは、リセット時に無効化されるため、初期化処理でアクセス権設定と稼働設定が必要です。NEONコプロセッサが無効状態でNEON命令を実行した場合、「未定義命令例外」が発生します。

【NEONコプロセッサ稼働プログラム例】

								;====================================================================
								; CP10/CP11のアクセス許可
								;====================================================================
								    MRC   p15, 0, r0, c1, c0, 2   ; コプロセッサアクセス制御レジスタ(CPACR)リード
								    ORR   r0, r0, #(0xF << 20)   	; CP10/11のフルアクセス設定
								    MCR   p15, 0, r0, c1, c0, 2   ; コプロセッサアクセス制御レジスタ(CPACR)ライト
								    ISB
								;====================================================================
								; VFPをNEON動作開始
								;====================================================================
								    MOV   r0, #0x40000000         ;
								    VMSR   FPEXC, r0              ; 浮動小数点例外レジスタでENビットを設定
								

NEONベクタ命令セット

NEONベクタ命令セットはアセンブリ命令がVで始まり、処理命令以外に演算結果のサイズやデータタイプを設定し、レジスタ幅とデータタイプで演算処理を行うレーン数が決まります。例えば、qレジスタ(128ビット)でデータサイズが8ビットの場合、16レーンとなり、同時に16回の演算処理を行うことができます。

								V{<mod>}<op>{<shape>}{<cond>}{.<dt>} (<dest>},src1,src2
								
設定項目 設定内容
命令修飾<mod> Q:飽和算術演算を行います。(例:VQADD)
H:結果を半分にします。(例:VHADD)
D:結果を2倍にします。(例:VQDMUL)
R:結果の丸めを行います。(例:VRHADD)
命令処理<op> 操作(例: ADD、SUB、MULなど)
<shape> L:2つのオペランドは2倍のビット幅になります。
W:最後のオペランドは2倍のビット幅になります。
N:結果は半分のビット幅になります。
条件<cond> 条件命令を実行します(Thumb2命令 ITブロックで使用されます)。
データタイプ<dt> データ型を指定します。
符号なし整数、U8、U16、U32、U64
符号付き整数、S8、S16、S32、S64
型指定なしの整数、I8、I16、I32、I64
浮動小数点数値、F16、F32
多項式、P8、P16
dest デスティネーション
src1 ソースオペランド1
src2 ソースオペランド2

ARMレジスタからNEONレジスタへのデータ転送命令

ARMレジスタからNEONレジスタにデータ転送を行います。d0レジスタの下位32ビットにARMレジスタのr1レジスタの内容を転送し、d0レジスタの上位32ビットにARMレジスタのr0レジスタの内容を転送します。

								VMOV  d0,r0,r1    ; d0=r0(上位32ビット)+r1(下位32ビット)
								
図4

ARMレジスタの内容をNEONレジスタへのコピー命令

ARMレジスタをNEONレジスタに32ビット単位でコピーします。

								VDPU.32   q0,r0    ; r0レジスタを32ビット毎にq0レジスタにコピー
								
図5

加算命令

16ビットデータ×4の加算命令は、16ビットレーンごとに加算処理を行います。

								VADD.I16  d2,d1,d0   ; d2 = d1 + d0(16ビットレーンごとに加算)
								
図6

ロード命令

メモリからNEONレジスタに読み込みを行う場合、ARMレジスタに読み込み開始アドレスを設定します。r0レジスタが示すアドレス(0x80000000番地)から32ビット単位でd0、d1、d2レジスタに読み込みを行い、[r0]に!を設定することで、r0レジスタ(読み込みアドレス)の更新が可能です(アドレス更新を行う場合、r0レジスタは、0x80000018になります)。

								VLD1.32   {d0,d1,d2},[r0]    ; r0レジスタが示すアドレスから読み込み
								
図7

ストア命令

NEONレジスタをメモリに書き込みを行う場合、ARMレジスタに読み込み開始アドレスを設定します。r0レジスタが示すアドレス(0x80000000番地)に32ビット単位でd0、d1、d2レジスタの値を書き込み、[r0]に!を設定することで、r0レジスタ(書き込みアドレス)の更新が可能です(アドレス更新を行う場合、r0レジスタは、0x80000018になります)。

								VST1.32   {d0,d1,d2},[r0]    ; r0レジスタが示すアドレスに書き込み
								
図8

NEONコプロセッサプログラミング

NEONコプロセッサを使用する場合、既存プログラムを修正してNEONベクタ命令を生成(自動ベクトル化)する方法、NEON組み込み関数を使用する方法、アセンブリ言語でNEONベクタ命令の使用する方法の3種類から選択することができます。アセンブリ命令は、ARMプロセッサのパイプラインを考慮したプログラミングが必要となり、コーディングが難しいため、自動ベクトル化とNEON組み込み関数の使用する方法をお勧めします。

ARMコンパイラの自動ベクトル化

ARMコンパイラは、コンパイラオプションの設定とC/C++ソースコードを変更することで、NEONベクタ命令を生成します。「--diag_warning=optimizations」オプションを設定することで、最適化に関する診断メッセージが出力されます。

【ARMコンパイラオプション設定】
No 設定項目 設定値
1 最適化設定値 「-O2-Otime」または「-O3 -Otime」を設定します。
2 NEONベクタ命令設定 「--vectorize」を設定します。
3 プロセッサ設定値 NEONコプロセッサ搭載ARMプロセッサを設定します。
例:「--cpu Cortex-A9」
【ソースコードの変更点】
No 変更ポイント
1 行数の少ない単純なループにして下さい。
2 ループからbreak文で抜けないで下さい。
3 ループの回数を2nにして下さい。
4 ループ回数が特定できるようにしてください。
5 ループ内関数は、インライン化することをお勧めします。
6 ポインタはインデックス[]を使用してください。
7 メモリ空間がオーバーラップしないために、__restrictキーワード(*3)を使用します。
  • (*3)__restrictとは、さまざまなオブジェクトのポインタ型や関数パラメータ配列が、重複するメモリ領域を使用しないことをコンパイラに指示する設定です。

自動ベクトル化例

float型配列の加算処理を自動ベクトル化できるようにソースコードを変更します。

【変更前ソースコード】

  • ①コンパイラオプションに「-O2 -Otime –vectorize –cpu=Cortex-A9」を設定します。
  • ②上記【ソースコードの変更点】を参考にプログラムを修正します。
								void float_add(float *fres,float *fdata1,float *fdata2)
								{
								    unsigned long i;

								    for(i=0;i<128;i++){
								        *fres = (*fdata1)+(*fdata2);
								        fres++;
								        fdata1++;
								        fdata2++;
								    }
								}
								

【変更後ソースコード】
次の変更を行うことで、ベクトル演算が可能となります。

  • ①引数のポインタ型に__restrict属性を設定します。
  • ②ポインタアクセスからインデックスアクセスに変更します。
図9

組み込み関数によるベクトル化

自動ベクトル化ができない場合、組み込み関数を使用してベクトル化します。2つのfloat型配列の合計値を求めるプログラムをNEON組み込関数でベクトル化します。

【変更前ソースコード】
本プログラムは、自動ベクトル化できません。筆者は、res変数で配列の合計値を求めているので、並列演算を行うことができないのでは?と考えています。そこで、NEON組み込み関数を使用してベクトル化します。

								float calc(float * __restrict fdata1,float * __restrict fdata2)
								{
								    float res=0.0;
								    unsigned long i;

								    for(i=0;i<128;i++){
								        res += fdata1[i]+fdata2[i];
								    }
								    return res;
								}
								

【変更後ソースコード(*4)】
NEON組み込み関数でベクトル演算を行います。

  • ①arm_neon.hヘッダファイルをインクルード。
  • ②単精度浮動小数演算(float型)を行うので、float32x4_tの変数を定義(NEONコプロセッサのqレジスタ(128ビット幅)として利用)。
  • ③演算回数は、1回で4回の演算を行うので、128/4=32回に変更。
  • ④vld1q_f32()組み込み関数を使用して、配列の先頭から4個のfloat型データをqレジスタに読み込み。
  • ⑤vaddq_f32()組み込み関数を使用して、4個のfloat型変数の加算。
  • ⑥vgetq_lane_f32()組み込み関数で、レーン毎の合計値を求める。
  • ⑦ポインタを更新。
図10
  • (*4)変更前ソースコードと演算の順番が異なりますので、演算精度の影響で同一の結果とならない場合があります。

まとめ

Cortex-Aシリーズには、NEONコプロセッサが搭載されているので、演算処理をSIMD演算に変更することで高速化が可能となるため、オープンソースの音声コーデックプログラムの自動ベクトル化やNEON組み込み関数を使用したチューニングを行いました。その結果、実行速度を向上することは簡単ではないことを実感することができました(逆に、性能が低下する問題が発生し困ったことがありました)。そこで、筆者が感じたNEONコプロセッサを有効に使うための考慮点をまとめてみました。

  • ①大量データの演算処理を行うためには、NEONコプロセッサが効率的に読み込み・書き込みができるデータ構造にすることが重要です(VLD/VST命令の動作の理解が必要です)。
  • ②NEON組み込み関数では、多くの変数定義により使用できるNEONレジスタがなくなった場合、NEONレジスタをスタックに退避・復帰を行いますので、実行速度が低下します。
  • ③自動ベクトル化は、適切なソースコードに変更しない限り行われることはありません。既存ソースコードの多くは、SIMD演算を想定してプログラムは作成されてない場合が多いと考えます。
  • ④PMU(第16回参照)を使用して実行時間を測定し、修正内容に対する実行時間の変化の把握が必要です。

NEONコプロセッサは上手に使用すると、演算処理を効率化できますので、チャレンジしてみてはいかがでしょうか。

参考資料

NEONコプロセッサの使用法を学ぶためには、次の3種類のマニュアルを参照ください。