約半年(またはそれ以上)前に、サウンド処理を主なタスクとするアプリケーションを作成する必要がありました。 このために、これをすべて行う簡単なエンジンが作成されました。 アプリケーションはリリースされ、徐々にこのエンジンはこの種の他のアプリケーションで頻繁に使用されるようになりました。 しかし最近、このプログラムの第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でテストされました。