TMS320C28x C/C++ コンパイラは、さまざまな最適化の技法を使用して C/C++ プロ
グラムの実行速度を向上させ、サイズを縮小します。最適化は、コンパイラ全体を 通じてさまざまなレベルで行われます。
ここで説明する最適化の大半は、-O コンパイラ・オプションを使って有効化および 制御する独立したオプティマイザ・パスによって実行されます(3.1 節(3-2 ページ)
を参照)。しかし、コード・ジェネレータは、選択的に有効化または無効化できない 一部の最適化を実行します。
コンパイラによって行われる最適化の種類を次に示します。このような最適化を行 うことにより、C/C++ コードの効率性が高くなります。
最適化 ページ
コストに基づいたレジスタ割り当て 3-21
エイリアスの明確化 3-22
データ・フローの最適化
❏ 複写伝播
❏ 共通部分式の除去
❏ 冗長な代入の除去
3-22
式の簡略化 3-23
ランタイムサポート・ライブラリ関数のインライン展開 3-24
誘導変数の最適化と強度換算 3-26
ループ不変コードの移動 3-26
ループの循環 3-26
レジスタ変数 3-26
レジスタ・トラッキング / レジスタ・ターゲッティング 3-26
実行できる最適化の種類
3.9.1 コストに基づいたレジスタ割り当て
コンパイラは最適化が有効になっている場合、ユーザ変数やコンパイラの一時値に、
それらの型、使用状況、使用頻度に応じてレジスタを割り当てます。ループの中で 使用されている変数は、他の変数より高い優先順位が割り当てられます。他の変数 と重複して使用されない変数は同じレジスタに割り当てられる場合があります。
次の例では、誘導変数 i は除去されています。これにより、カウントするループを
実装するRPT 命令を使用できます。強度換算は、配列参照を、自動インクリメント
と組み合わせた効率的なポインタ参照にします。
例 3-4. 強度換算、誘導変数の除去、レジスタ変数 (a) C ソース
(b) コンパイラ出力 int a[10];
main () {
int i;
for (i = 0; i < 10; i++) a[i] = 0;
}
;***************************************************************
;* FNAME: _main FR SIZE: 0 *
;* *
;* FUNCTION ENVIRONMENT *
;* *
;* FUNCTION PROPERTIES *
;* 0 Parameter, 0 Auto, 0 SOE *
;***************************************************************
_main:
;*** --- #pragma MUST_ITERATE(10, 10, 10)
;*** --- U$4 = &a[0];
実行できる最適化の種類
3.9.2 エイリアスの明確化
一般に、C/C++ プログラムでは多数のポインタ変数が使用されます。多くの場合、コ
ンパイラは 2 つ以上の l 値(小文字の L:シンボル、ポインタ参照、または構造体参 照)が同じメモリ位置を参照しているかどうかを判断できません。このメモリ位置 のエイリアス指定により、コンパイラがレジスタ内に値を保持できなくなる場合が 少なくありません。その理由は、時間が経過するとレジスタとメモリが同じ値を保 持し続けているかどうかを保証できないからです。
エイリアスの明確化は、2 つのポインタ式が同じ位置を指す可能性がなくなる時期を 判別する技法です。これを使用すると、コンパイラは自由にそれらの式を最適化で きるようになります。
3.9.3 データ・フローの最適化
以下のデータ・フローの最適化では、効率的な式への置換、不要な代入の検出と除 去、および計算済みの値を求める演算の排除をまとめて行います。最適化が有効に なっているコンパイラは、これらのデータ・フローの最適化をローカル(基本ブロッ ク内で)とグローバル(全関数に対して)の両面から行います。
❏ 複写伝播
変数への代入が終わると、コンパイラはその変数の参照を代入された値に置換し ます。この値は、別の変数、定数、または共通部分式の場合もあります。その結 果、定数の畳み込みや共通部分式の除去、または変数全体の除去にも十分に活用 できます。例 3-5(3-23 ページ)を参照してください。
❏ 共通部分式の除去
複数の式から同じ値が得られる場合、コンパイラは値を一度だけ計算し、その値 を保存し、再利用します。
❏ 冗長な代入の除去
多くの場合、複写伝播と共通部分式の除去による最適化の結果、変数(それ以 降、別の代入があるまで、または関数が終了するまで次の参照がない変数)への 不要な代入が生じます。コンパイラは、このような不要な代入を除去します(例
3-5 を参照)。
実行できる最適化の種類
3.9.4 式の簡略化
最適な計算ができるように、コンパイラは式を簡略化し、命令やレジスタをほとん ど必要としない同等の書式に置換します。定数間の演算では単一の定数に畳み込ま れます。たとえば、a = (b + 4) - (c + 1) は a = b - c +3 になります(例 3-5 を参照)。
例 3-5 では、a に代入された定数 3 は a を使用するすべての場所に複写伝播され、
a は不要な変数となり、除去されます。j に 3 を乗算した積と j に 2 を乗算した積の 和は、簡略化されて b = j * 5 となります。これは、共通部分式として認識されます。
c や d への代入は不要で、その式と置き換えられます。
例 3-5. データ・フローの最適化と式の簡略化 (a) C ソース
(b) コンパイラ出力
int simplify (int j) {
int a = 3;
int b = (j * a) + (j * 2);
return b;
}
;***************************************************************
;* FNAME: _simplify FR SIZE: 0 *
;* *
;* FUNCTION ENVIRONMENT *
;* *
;* FUNCTION PROPERTIES *
;* 0 Parameter, 0 Auto, 0 SOE *
;***************************************************************
_simplify:
;*** 5 --- return j*5;
MOV T,AL ; |5|
実行できる最適化の種類
3.9.5 関数のインライン展開
コンパイラは、小さなランタイムサポート関数の呼び出しをインライン・コードに 置換することにより、関数呼び出しに関連したオーバーヘッドを減らすと同時に他 の最適化を適用できる機会を増やします(例 3-6 を参照)。
例 3-6 では、コンパイラは C 関数 toupper( ) に対応するコードを見つけ、この関数の
呼び出しをコードに置換します。
例 3-6. 関数のインライン展開 (a) C ソース
#include <ctype.h>
char *strupper (char *s) {
char *cp;
for (cp = s; *cp; cp++) *cp = toupper (*cp) return s;
}
実行できる最適化の種類
例 3-6. 関数のインライン展開(続き)
(b) コンパイラ出力
;***************************************************************
;* FNAME: _strupper FR SIZE: 0 *
;* *
;* FUNCTION ENVIRONMENT *
;* *
;* FUNCTION PROPERTIES *
;* 0 Parameter, 0 Auto, 0 SOE *
;***************************************************************
_strupper:
;*** 7 --- cp = s;
;*** 7 --- goto g4;
MOVZ AR6,AR4 ; |7|
B L3,UNC ; |7|
; branch occurs ; |7|
l!:
;*** ---g1:
;*** 144 --- ch = C$2; // [0]
;*** 152 --- if ( (unsigned)ch-97u >25u ) goto g3;
MOV AH,AL ; |152|
ADDB AH,#-97
CMPB AH,#25 ; |152|
B L2,HI ; |152|
; branch occurs ; |152|
;*** 152 --- ch -= 32; // [0]
ADDB AL,#-32 L2:
;*** ---g3:
;*** 153 --- *cp++ = ch; // [0]
MOV *XAR6++,AL ; |153|
L3:
;*** ---g4:
;*** 8 --- if ( C$2 = *cp ) goto g1;
MOV AL,*+XAR6[0] ; |8|
実行できる最適化の種類
3.9.6 誘導変数と強度換算
誘導変数とは、ループ内での値がループの実行回数に直接関係する変数のことです。
ループでの配列のインデックスと制御変数は、多くの場合誘導変数です。
強度換算とは、誘導変数を含んでいる非効率的な式をより効率的な式に置換するプ ロセスのことです。たとえば、インデックスを使用して一連の配列要素を参照する コードは、ポインタを使用してその配列をインクリメントするコードに置換されま す。
誘導変数の解析と強度換算を併用すると、多くの場合ループ制御変数へのすべての 参照が除去され、ループ制御変数を除去することができます。
3.9.7 ループ不変コードの移動
この最適化では、ループ内で常に同じ値に算出する式が特定されます。その計算は、
ループの前に移動され、ループ内の個々の式は事前に計算された値への参照に置き 換えられます。
3.9.8 ループの循環
コンパイラはループの最後でループ条件式を計算し、ループ外への余分な分岐が発 生しないようにします。多くの場合、最初のエントリでの条件式のチェックと分岐 は最適化から除去されます。
3.9.9 レジスタ変数
コンパイラは、ローカル変数、パラメータ、および一時値を格納するためにレジス タを最大限に使用することができます。メモリ内の変数をアクセスするより、レジ スタに格納されている変数をアクセスする方が効率的です。レジスタ変数はポイン タで特に効率的です(例 3-4(3-21 ページ)を参照)。
3.9.10 レジスタ・トラッキング / レジスタ・ターゲッティング
レジスタの内容がすぐに使用される場合に、コンパイラはその内容を記録し、値を リロードしないようにします。(a.b) などの変数、定数、および構造体参照は、直線 的コードから記録されます。また、レジスタ・ターゲッティングは、レジスタ変数 に代入する場合、または関数から値を戻す場合など必要に応じて特定のレジスタへ
実行できる最適化の種類
例 3-7. レジスタ・トラッキング / レジスタ・ターゲッティング (a) C ソース
(b) コンパイラ出力 int x, y;
main() {
x *= 3;
y = x;
}
;***************************************************************
;* FNAME: _main FR SIZE: 0 *
;* *
;* FUNCTION ENVIRONMENT *
;* *
;* FUNCTION PROPERTIES *
;* 0 Parameter, 0 Auto, 0 SOE *
;***************************************************************
_main:
;*** 5 --- x *= 3;
;*** 6 --- y = x;
;*** 6 --- return;
MOVZ DP,#_x ; |5|
MOV T,@_x ; |5|
MPYB ACC,T,#3 ; |5|
MOV @_x,AL ; |6|
LRETR
; return occurs
実行できる最適化の種類
3.9.11 テール結合
コード・サイズを最適化する場合、一部の関数についてはテール結合が非常に効率 的です。テール結合により、同じ命令シーケンスで終了し、共通の宛先をもつ基本 ブロックを検出します。このようなブロックが検出されると、同じ命令シーケンス がそれ自身のブロックになります。その後、これらの命令は一連のブロックから削 除され、新しく作成されたブロックへの分岐に置き換えられます。そのため、セッ ト内の各ブロックについて 1 つではなく、命令シーケンスのコピーが 1 つだけあり ます。
例 3-8 では、3 つのケースの最後で a への加算が 1 つのブロックに結合されていま
す。また、1 番目と 2 番目のケースでは 3 を乗算して別のブロックに結合されていま す。これは結果的に、3 つの命令を換算したものになります。場合によっては、この 最適化は余分な分岐を導入したことにより、実行スピードに悪影響を及ぼします。
例 3-8. テール結合 (a) C コード
int func(int a) {
if (a < 0) { a = -a;
a += f(a)*3;
}
else if (a == 0) {
a = g(a);
a += f(a)*3;
} else
a += f(a);
return a;
}