アプリケーションの最適化(Iphone armv6)

ごく最近、最初のアプリケーションがAppleStoreに登場してから1年になりました。 最初は、それを理解するのはかなり困難でした。 特に、それ以前に私がMacOSのアプリケーション開発に関与していなかったことを考えると。 今年は多くのことが書かれています。 残念ながら、私たちが書いたアプリケーションに名前を付けることはできません(私は誰も覚えていませんし、マニュアルはそのようなことを承認していません)。

約半年(またはそれ以上)前に、サウンド処理を主なタスクとするアプリケーションを作成する必要がありました。 このために、これをすべて行う簡単なエンジンが作成されました。 アプリケーションはリリースされ、徐々にこのエンジンはこの種の他のアプリケーションで頻繁に使用されるようになりました。 しかし最近、このプログラムの第2バージョンの開発が開始されました。 要件は増加しており、古いiPhoneのリソースは変更されていません。 ここでは、すでに記述されたコードを改善する方法を探す必要がありました。





コンパイラー設定(サム)





最初に思い浮かぶのは、コンパイラーができることをすべて絞り出そうとすることです。 おそらくここで変更できる最も重要なパラメーターは、アプリケーションをサムモード用にコンパイルすることです。 このモードが有効になっている場合、タスクの実行に使用されるコマンドの数が少なくなります。 この命令セットはよりコンパクトなコードにエンコードされますが、すべてのプロセッサリソースを使用できるわけではありません。 特に、VFPは直接使用できません。 浮動小数点数の操作を行う場所では、次のようなコードを見つけることができます。



double prevTime=CFAbsoluteTimeGetCurrent();

{

...

}

double nextTime=CFAbsoluteTimeGetCurrent();

double dt = nextTime-prevTime;

printf( "dt=%f" ,dt);




* This source code was highlighted with Source Code Highlighter .








コンパイル後、次のようになります。



blx L_CFAbsoluteTimeGetCurrent$stub

mov r5, r1

blx L_CFAbsoluteTimeGetCurrent$stub

mov r3, r5

mov r2, r4

blx L___subdf3vfp$stub

ldr r6, L7

mov r2, r1

mov r1, r0

mov r0, r6

blx L_printf$stub




* This source code was highlighted with Source Code Highlighter .








親指モードではない場合、コードは次のようになります。



bl L_CFAbsoluteTimeGetCurrent$stub

fmdrr d8, r0, r1

bl L_CFAbsoluteTimeGetCurrent$stub

fmdrr d6, r0, r1

ldr r0, L7

fsubd d7, d6, d8

fmrrd r1, r2, d7

bl L_printf$stub




* This source code was highlighted with Source Code Highlighter .








ご覧のとおり、違いは非常に重要です。 追加の関数呼び出しはなく、すべての浮動小数点演算は適切な場所で行われ、遠く離れた場所ではなく、私たちとは行われません。 おそらく質問がありますが、より速く動作しますか? 当然のことながら、答えは「はい」です。 プログラムが重い計算を実行しない場合でも、実行されます。 親指モードのプラスとして-よりコンパクトなコード。これは、理論的にはプログラムの読み込みが速くなることを意味します。

ちなみに、Xcodeツールでは各ファイルに個人的なパラメーターを設定することができ、プロジェクトの個々のフラグメントに対してのみサムモードをオフ(またはその逆)にできます。これは非常に便利です。



アルゴリズムの最適化



計算を高速化する次のステップは、できるだけ多くの浮動小数点演算を破棄することです。 代わりに、数値を整数に変換し、特定の係数を掛けます。 当然、必要なデータを取得するのに便利になるように、2のべき乗の係数を選択することをお勧めします。

さて、重要な場所ですべてのプロセッサリソースを使用するようコンパイラに強制し、可能であれば浮動小数点演算を削除しました。 ここで、ArmV6の仕様を見てみましょう(たとえば、 こちら )。 関数の説明を注意深く読むと、多くの興味深いコマンドを見ることができます(それらの多くは、サムモードでは使用できません)。

たとえば、単純なローパスまたはハイパスフィルターを作成するタスクがあります。 アルゴリズムは、最終的に次の式を計算することになります。

tmp = b0*in0+b1*in1+b2*in2 -a1*out1-a2*out2;



* This source code was highlighted with Source Code Highlighter .






(b0、b1、b2、a1、a2は、所定のカットオフ周波数での定数です)



次に、smladコマンドの説明を見てください。 このコマンドは、2つの16ビット数の乗算を実行し、指定した結果とレジスタを要約します。 式は次のようになります(ビットは角括弧で示されます)。



result[0:31] = a[0:15]*b[0:15] + a[16:31]*b[16:31] + [0:31]



* This source code was highlighted with Source Code Highlighter .








つまり 数式自体の計算は、3つの操作で実行できます。 この関数の使用方法の問題を解決するためだけに残っています。 Dosiの時代からアセンブラーで多くの経験があり、gccでは、アセンブラーで書かれた挿入は素晴らしく機能します。 一般に、このコマンドを使用する関数を作成します。



inline volatile int SignedMultiplyAccDual(int32_t x, int32_t y, int32_t addVal)

{

register int32_t result;

asm volatile ( "smlad %0, %1, %2, %3"

: "=r" (result)

: "r" (x), "r" (y), "r" (addVal)

);

return result;

}




* This source code was highlighted with Source Code Highlighter .








ところで、便宜上、シミュレータ用の関数のバージョンを作成できます。 そして、テストは便利ではありません。 私はこのようになった:



#if defined __arm__

inline volatile int SignedMultiplyAccDual(int32_t x, int32_t y, int32_t addVal)

{

register int32_t result;

asm volatile ( "smlad %0, %1, %2, %3"

: "=r" (result)

: "r" (x), "r" (y), "r" (addVal)

);

return result;

}



inline volatile int SignedMultiplyAcc(int32_t x, int32_t y, int32_t addVal)

{

register int32_t result;

asm volatile ( "mla %0, %1, %2, %3"

: "=r" (result)

: "r" (x), "r" (y), "r" (addVal)

);

return result;

}



#else



inline volatile int SignedMultiplyAcc(int32_t x, int32_t y, int32_t addVal)

{

register int32_t result;

result = x*y+addVal;

return result;

}



inline volatile int SignedMultiplyAccDual(int32_t x, int32_t y, int32_t addVal)

{

register int32_t result;

result = int16_t(x & 0x0000FFFF) * int16_t(y & 0x0000FFFF);

result += int16_t(x >> 16) * int16_t(y >> 16);

result += addVal;

return result;

}

#endif




* This source code was highlighted with Source Code Highlighter .








その結果、式の計算は次のようになります。

tmp = fParamsHigh[0]*fValsHigh[0];

tmp = SignedMultiplyAccDual(*(int32_t *)&fParamsHigh[1],*(int32_t *)&fValsHigh[1],tmp);

tmp = SignedMultiplyAccDual(*(int32_t *)&fParamsHigh[3],*(int32_t *)&fValsHigh[3],tmp);

tmp = tmp >> PARAMS_SHL_VAL;




* This source code was highlighted with Source Code Highlighter .








dysasmを見てみましょう。

ldrh r3, [r4, #196]

ldrh r0, [r4, #206]

ldr r2, [r4, #208]

smulbb r3, r3, r0

smlad r3, r1, r2, r3

ldr r1, [r4, #202]

ldr r2, [r4, #212]

smlad r3, r1, r2, r3

mov r3, r3, asr #10




* This source code was highlighted with Source Code Highlighter .








すべてが美しく明確です。 私の友人が言ったように、「ダウンロードしました。 実現した。 アップロードしました。 吐き出します。」 以前は見なかったほうがいいです。 ただひどかった。 したがって、私のプログラムには、遅延効果があった2つのチャンネルがありました。 そのような効果ごとに、2つのフィルターが必要でした(1つはローパスフィルター、もう1つはハイパスフィルター)。 合計4つのフィルター。 最適化の後、Instrumentsのプロセッサ負荷を見ると、プログラムは〜45%ではなく、プロセッサ時間の〜35%を消費していることがわかります。 かなり悪い結果:)

ちなみに、ドキュメントを読んだ後、整数除算がないことに驚いた。 その結果、線形補間アルゴリズム(すべてのアクティブなチャネルでのリサンプリングに使用)がわずかに変更されたため、負荷は通常〜30%に低下しました:)

これにより、いくつかの単純でかなり明白な最適化により、プロセッサの負荷が約1/3削減されました。

PSすべてがiPhone 3gでテストされました。



All Articles