[解決済み] C 言語の配列に値が存在するかどうかを素早く見つけるには?
質問
サイズ256(できれば1024、ただし256が最小)の配列を繰り返し処理し、値が配列の内容に一致するかどうかをチェックする必要があるタイムクリティカルなISRを持つ組み込みアプリケーションを使用しています。A
bool
は true に設定されます。
マイクロコントローラはNXPのLPC4357、ARM Cortex M4コアで、コンパイラはGCCです。私はすでに最適化レベル 2 (3 はより遅い) を組み合わせ、関数をフラッシュではなく RAM に配置しました。また、ポインタ演算と
for
ループを使い、アップカウントの代わりにダウンカウントをしています(もし
i!=0
をチェックする方が
i<256
). 結局、12.5μsの時間がかかってしまいましたが、これは実現可能なように大幅に削減する必要があります。これは私が現在使用している(擬似)コードです。
uint32_t i;
uint32_t *array_ptr = &theArray[0];
uint32_t compareVal = 0x1234ABCD;
bool validFlag = false;
for (i=256; i!=0; i--)
{
if (compareVal == *array_ptr++)
{
validFlag = true;
break;
}
}
絶対的に最速の方法は何でしょうか?インラインアセンブリの使用は許可されています。他の「あまりエレガントでない」トリックも許可されます。
どのように解決するのですか?
パフォーマンスが最も重要な状況では、C コンパイラーは、手で調整したアセンブリ言語でできることに比べて最速のコードを生成しないことがほとんどです。このような小さなルーチンでは、asm コードを書くだけで、実行にかかるサイクル数を把握することができます。Cのコードをいじってコンパイラに良い出力を出させることはできるかもしれませんが、その方法では出力を調整するのに多くの時間を浪費することになるかもしれません。コンパイラ(特にマイクロソフト社製)はここ数年で大きく進歩しましたが、一般的なケースだけでなく、あなたの特定の状況で作業しているので、あなたの耳の間のコンパイラほどにはまだ賢くはないのです。コンパイラは、これを高速化できる特定の命令(例えばLDM)を利用しないかもしれませんし、ループをアンロールするほど賢くはないでしょう。ここで、私がコメントで述べた3つのアイデアを取り入れた方法を紹介します。ループのアンロール、キャッシュプリフェッチ、マルチロード(ldm)命令の活用です。命令サイクル数は配列要素あたり約3クロックになりますが、これはメモリの遅延を考慮したものではありません。
動作原理は? ARMのCPU設計は、ほとんどの命令を1クロックサイクルで実行しますが、命令はパイプラインで実行されます。C コンパイラーは、間に他の命令を挟むことで、パイプラインの遅延をなくそうとします。元のC言語のコードのようにタイトなループの場合、メモリから読み込んだ値をすぐに比較しなければならないので、コンパイラは遅延を隠すのに苦労することになります。私のコードでは、2組の4つのレジスタを交互に使用することで、メモリ自体の遅延とデータのフェッチにかかるパイプラインの遅延を大幅に削減しています。一般に、大きなデータセットを扱うときに、コードが利用可能なレジスタのほとんどまたはすべてを使用しない場合、最大のパフォーマンスを得ることはできません。
; r0 = count, r1 = source ptr, r2 = comparison value
stmfd sp!,{r4-r11} ; save non-volatile registers
mov r3,r0,LSR #3 ; loop count = total count / 8
pld [r1,#128]
ldmia r1!,{r4-r7} ; pre load first set
loop_top:
pld [r1,#128]
ldmia r1!,{r8-r11} ; pre load second set
cmp r4,r2 ; search for match
cmpne r5,r2 ; use conditional execution to avoid extra branch instructions
cmpne r6,r2
cmpne r7,r2
beq found_it
ldmia r1!,{r4-r7} ; use 2 sets of registers to hide load delays
cmp r8,r2
cmpne r9,r2
cmpne r10,r2
cmpne r11,r2
beq found_it
subs r3,r3,#1 ; decrement loop count
bne loop_top
mov r0,#0 ; return value = false (not found)
ldmia sp!,{r4-r11} ; restore non-volatile registers
bx lr ; return
found_it:
mov r0,#1 ; return true
ldmia sp!,{r4-r11}
bx lr
更新しました。 コメントには、私の経験が逸話的/価値がないと考え、証拠を必要とする懐疑的な人がたくさんいます。私は GCC 4.8 (Android NDK 9C から) を使用して、最適化 -O2 (すべての最適化をオン) で次の出力を生成しました。 ループアンロールを含む ). 私は上記の質問で提示されたオリジナルのCコードをコンパイルしました。以下は、GCCが生成したものです。
.L9: cmp r3, r0
beq .L8
.L3: ldr r2, [r3, #4]!
cmp r2, r1
bne .L9
mov r0, #1
.L2: add sp, sp, #1024
bx lr
.L8: mov r0, #0
b .L2
GCCの出力はループをアンロールしないだけでなく、LDRの後のストールで1クロックを浪費しています。配列要素ごとに少なくとも8クロックを必要とします。ループを終了するタイミングを知るためにアドレスを使用するのは良い仕事ですが、コンパイラができる魔法のようなことはこのコードではどこにも見当たりません。ターゲット プラットフォーム上でコードを実行したことはありませんが (私は所有していません)、ARM コードのパフォーマンスの経験者なら誰でも、私のコードが高速であることを確認できます。
アップデート 2。 MicrosoftのVisual Studio 2013 SP2に、このコードでもっとうまくやる機会を与えてみました。それは私の配列初期化をベクトル化するためにNEON命令を使用することができましたが、OPによって書かれた線形値検索はGCCが生成したものに似ていました(私はより読みやすくするためにラベルの名前を変更しました)。
loop_top:
ldr r3,[r1],#4
cmp r3,r2
beq true_exit
subs r0,r0,#1
bne loop_top
false_exit: xxx
bx lr
true_exit: xxx
bx lr
申し上げたように、私は OP の正確なハードウェアを所有していませんが、3 つの異なるバージョンの nVidia Tegra 3 および Tegra 4 でパフォーマンスをテストし、その結果をすぐにここに投稿する予定です。
アップデート 3。
アップデート 3 私のコードと Microsoft のコンパイル済み ARM コードを Tegra 3 と Tegra 4 (Surface RT、Surface RT 2) で実行しました。一致を見つけるのに失敗するループの 1000000 回の繰り返しを実行したので、すべてがキャッシュにあり、測定が簡単です。
My Code MS Code
Surface RT 297ns 562ns
Surface RT 2 172ns 296ns
どちらの場合も、私のコードはほぼ2倍の速度で実行されます。ほとんどの最新の ARM CPU では、おそらく同様の結果が得られるでしょう。
関連
-
[解決済み】JavaでMap値をインクリメントする最も効率的な方法
-
[解決済み] c または c++ 用のシンプルな 2 次元クロスプラットフォームグラフィックスライブラリ?[クローズド]
-
[解決済み] C言語で配列のサイズを決定するにはどうすればよいですか?
-
[解決済み] 配列のすべてのメンバーを同じ値で初期化するには?
-
[解決済み] プログラムによってマシンのコア数を求める
-
[解決済み] C 言語の配列へのポインタ/ポインタの配列の曖昧さ解消
-
[解決済み] コピーエリジョンと戻り値の最適化とは何ですか?
-
[解決済み] 2次元配列の反復処理において、ループの順序がパフォーマンスに影響するのはなぜですか?
-
[解決済み] sizeof'(配列を指すポインタ)を見つけるにはどうしたらいいですか?
-
[解決済み】glibcのstrlenはなぜこんなに複雑でないと高速に実行できないのか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
C/C++の再定義
-
[解決済み] C言語の**はどういう意味ですか?
-
[解決済み] "static const" vs "#define" vs "enum"
-
[解決済み] C - Setデータ構造を実装するには?
-
[解決済み] mallocの結果はキャストするのですか?
-
[解決済み] 演算子 *, /, +, -, % を使わずに 3 で割る。
-
[解決済み] C言語標準に準拠した構造体の初期化方法
-
[解決済み] なぜalloca()の使用はグッドプラクティスとみなされないのでしょうか?
-
[解決済み] ストラクチャーとユニオンの違い
-
[解決済み] FortranはC言語よりも重い計算を最適化しやすいですか?