5

ARM NEON 組み込み関数について学び、配列内の要素を 2 倍にするために記述した関数のタイミングを計っていました。組み込み関数を使用したバージョンは、単純な C バージョンの関数よりも時間がかかります。

ネオンなし:

    void  double_elements(unsigned int *ptr, unsigned int size)
 {
        unsigned int loop;
        for( loop= 0; loop<size; loop++)
                ptr[loop]<<=1;
        return;
 }

ネオンの場合:

 void  double_elements(unsigned int *ptr, unsigned int size)
{    
        unsigned int i;
        uint32x4_t Q0,vector128Output;
        for( i=0;i<(SIZE/4);i++)
        {
                Q0=vld1q_u32(ptr);               
                Q0=vaddq_u32(Q0,Q0);
                vst1q_u32(ptr,Q0);
                ptr+=4;

        }
        return;
}

配列とベクトルの間のロード/ストア操作に時間がかかり、並列追加の利点が相殺されているかどうか疑問に思っています。

更新: Igor の返信に対する詳細情報。
1.コードは次の場所に掲載されています:
plain.c
plain.s
ネオン.c ネオン
.s
両方のアセンブリ リストのセクション(ラベル) L7 から、ネオン バージョンの方がアセンブリ命令の数が多いことがわかります。
2.arm-gcc で -mfpu=neon を使用してコンパイルしました。他のフラグや最適化はありません。プレーン バージョンでは、コンパイラ フラグはまったくありません。
3.それはタイプミスでした。SIZE はサイズであることを意味していました。両方とも同じです。
4,5.4000要素の配列で試しました。関数呼び出しの前後に gettimeofday() を使用して時間を計りました.NEON=230us,ordinary=155us.
6.はい、それぞれの要素を印刷しました。
7.これを実行しましたが、まったく改善されませんでした。

4

3 に答える 3

3

命令ごとにより大量に処理し、ロード/ストアをインターリーブし、使用法をインターリーブします。この関数は現在、56 uintを2倍(左にシフト)します。

void shiftleft56(const unsigned int* input, unsigned int* output)
{
  __asm__ (
  "vldm %0!, {q2-q8}\n\t"
  "vldm %0!, {q9-q15}\n\t"
  "vshl.u32 q0, q2, #1\n\t"
  "vshl.u32 q1, q3, #1\n\t"
  "vshl.u32 q2, q4, #1\n\t"
  "vshl.u32 q3, q5, #1\n\t"
  "vshl.u32 q4, q6, #1\n\t"
  "vshl.u32 q5, q7, #1\n\t"
  "vshl.u32 q6, q8, #1\n\t"
  "vshl.u32 q7, q9, #1\n\t"
  "vstm %1!, {q0-q6}\n\t"
  // "vldm %0!, {q0-q6}\n\t" if you want to overlap...
  "vshl.u32 q8, q10, #1\n\t"
  "vshl.u32 q9, q11, #1\n\t"
  "vshl.u32 q10, q12, #1\n\t"
  "vshl.u32 q11, q13, #1\n\t"
  "vshl.u32 q12, q14, #1\n\t"
  "vshl.u32 q13, q15, #1\n\t"
  // lost cycle here unless you overlap
  "vstm %1!, {q7-q13}\n\t"
  : "=r"(input), "=r"(output) : "0"(input), "1"(output)
  : "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7",
    "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15", "memory" );
}

Neonの最適化で覚えておくべき重要なこと...2つのパイプラインがあります。1つはロード/ストア用(2つの命令キューがあります。1つは保留中、もう1つは実行中です。通常はそれぞれ3〜9サイクルかかります)、もう1つは算術演算用です( 2つの命令パイプライン。1つは実行中、もう1つは結果の保存)。これらの2つのパイプラインをビジー状態に保ち、命令をインターリーブする限り、非常に高速に動作します。さらに良いことに、ARM命令がある場合、レジスタに留まっている限り、NEONが完了するのを待つ必要はなく、同時に実行されます(キャッシュ内に最大8つの命令)。したがって、ARM命令にいくつかの基本的なループロジックを組み込むことができ、それらは同時に実行されます。

元のコードも、4つのうち1つのレジスタ値のみを使用していました(qレジスタには4つの32ビット値があります)。そのうちの3つは、明らかな理由もなく2倍の操作を行っていたため、4倍遅くなりました。

このコードでより良いのは、このループを処理し、vldm %0!, {q2-q8}次のvstm %1!...などを追加して埋め込まれたものを処理することです。また、結果を送信する前にもう1つの命令を待つので、パイプは他の何かを待つことはありません。最後に、に注意してください!。これはポストインクリメントを意味します。そのため、値の読み取り/書き込みを行ってから、レジスタからポインタを自動的にインクリメントします。ARMコードでそのレジスタを使用しないことをお勧めします。そうすれば、独自のパイプラインがハングすることはありません...レジスタを分離し、countARM側に冗長変数を設定してください。

最後の部分...私が言ったことは本当かもしれませんが、常にではありません。それはあなたが持っている現在のネオンリビジョンに依存します。タイミングは将来変わるかもしれませんし、いつもそうだったとは限りません。それは私のために働きます、ymmv。

于 2011-11-22T19:22:54.923 に答える
3

質問はかなり漠然としていて、多くの情報を提供していませんが、いくつかの指針を提供しようとします.

  1. アセンブリを見るまで、何が起こっているのかはわかりません。-S を使え、ルーク!
  2. コンパイラ設定を指定していません。最適化を使用していますか? ループ展開?
  3. 最初の関数は を使用しsize、2 番目は を使用しますSIZEが、これは意図的なものですか? 彼らは同じですか?
  4. 試した配列のサイズは?いくつかの要素については、NEON がまったく役に立たないと思います。
  5. 速度の違いは数パーセント?数桁?
  6. 結果が同じであることを確認しましたか?コードが同等であると確信していますか?
  7. 中間結果に同じ変数を使用しています。加算の結果を別の変数に保存してみてください。vshl_n_u32また、足し算の代わりにシフト ( ) を使用することもできます。

編集:答えてくれてありがとう。私は少し見回して、この議論を見つけました(強調は私のものです):

NEON から ARM レジスタへのデータの移動は Cortex-A8 のコストが高いため、Cortex-A8 の NEON は、ARM パイプラインの相互作用がほとんどない大きなブロックの作業に最適です。

あなたの場合、NEONからARMへの変換はありませんが、ロードとストアのみです。それでも、並列操作での節約は、非 NEON パーツによって食い尽くされているようです。色変換など、NEON で多くのことを行うコードでは、より良い結果が期待できます。

于 2011-04-19T13:51:34.520 に答える