5.6 C コードのループ終了の最適化

ループは、大半のプログラムに含まれている一般的な構文の 1 つです。ループの実行には多大な時間が費やされることが多いので、時間制限の厳しいループに注意を払うことを推奨します。

ループ終了条件は、多大なオーバーヘッドの原因となりかねないので、その作成にあたっては注意が必要です。可能な限り以下を実践して下さい。
  • 単純な終了条件を使用する。
  • ゼロまでカウントダウンするループを作成する。
  • unsigned int 型のカウンタを使用する。
  • ゼロと等しいかどうかをテストする。
任意のガイドラインまたはすべてのガイドラインに関して、個別にまたはそれらを組み合わせて従うことで、より優れたコードを作成できます。
以下の表に、n! を計算するルーチンの実装例を 2 つ示します。これらの例は共に、ループ終了のオーバーヘッドを示しています。最初の実装では、インクリメントループを使用して n! が計算されるのに対し、2 番目の実装では、デクリメントループを使用して n! が計算されます。

表 5-1 インクリメントループとデクリメントループを表す C コード

インクリメントループ デクリメントループ
int fact1(int n)
{
    int i, fact = 1;
    for (i = 1; i <= n; i++)
        fact *= i;
    return (fact);
}
int fact2(int n)
{
    unsigned int i, fact = 1;
    for (i = n; i != 0; i--)
        fact *= i;
    return (fact);
}
以下の表は、コンパイラによって生成されたマシンコードが上の各実装例でどのように逆アセンブルされるかを示したものです。いずれの実装の C コードも、オプション O2 -Otime を使用してコンパイルされています。

表 5-2 インクリメントループとデクリメントループを表す C 逆アセンブリコード

インクリメントループ デクリメントループ
fact1 PROC
    MOV      r2,r0
    MOV      r0,#1
    CMP      r2, #1
    MOV      r1, r0
    BXLT     lr
|L1.20|
    MUL      r0, r1, r0
    ADD      r1, r1, #1
    CMP      r1, r2
    BLE      |L1.20|
    BX       lr
    ENDP
fact2 PROC
    MOVS     r1, r0
    MOV      r0,#1
    BXEQ     lr
|L1.12|
    MUL      r0, r1, r0
    SUBS     r1, r1, #1
    BNE      |L1.12|
    BX       lr
    ENDP
逆アセンブリを比較すると、インクリメントループ逆アセンブリの命令ペア ADD および CMP が、デクリメントループ逆アセンブリの単一の SUBS 命令で置換されていることがわかります。これは、代わりにゼロとの比較を使用できるためです。
ループ内で命令を節約したことに加えて、変数 n をループをまたがって保存する必要はありません。そのため、デクリメントループ逆アセンブリではレジスタの使用も節約され、レジスタの割り当てが容易になります。これは、元の終了条件に関数呼び出しが含まれる場合には、より重要になります。例えば、
for (...; i < get_limit(); ...);
ループカウンタを必要な繰り返し数に初期化して、ゼロにデクリメントするテクニックは、 while ステートメントと do ステートメントにも適用されます。
関連する概念
5.7 C コードのループの展開
非機密扱いPDF file icon PDF 版ARM DUI0472LJ
Copyright © 2010-2015 ARM.All rights reserved.