開発にあたってのノウハウ
2. コード効率を上げるには
2.1 20bitアドレスモード設定を利用する
FRでは、演算の際は通常、以下の3ステップで処理を行います。
- メモリアドレスのレジスタ設定
- データのレジスタへのロード
- 演算
特に外部変数を多用する場合32ビットアドレスをロードする命令が多数出現するためコードサイズが大きくなるケースがあります。
| <Cソース> | <FRの場合> | |
| a=b+c; | LDI:32 | #_b, R12 |
| LD | @R12, R0 | |
| LDI:32 | #_c, R12 | |
| LD | @R12, R1 | |
| ADD | R1, R0 | |
| LDI:32 | #_a, R12 | |
| ST | R0, @R12 | |
そのため、コードあるいはデータが20bitアドレス空間(0x0~0xFFFFF)に配置されているRAM/ROMに配置可能である場合、20ビットアドレスモード(-K shortaddressオプション)の設定を推奨いたします。配置が不可の場合は、可能であれば、外部変数の利用をローカル変数へ変更してください。
| <Cソース> | <デフォルト> | <-Kshortaddress指定> | ||
| a=b+c; | LDI:32 | #_b, R12 | LDI:20 | #_b, R12 |
| LD | @R12, R0 | LD | @R12, R0 | |
| LDI:32 | #_c, R12 | LDI:20 | #_c, R12 | |
| LD | @R12, R1 | LD | @R12, R1 | |
| ADD | R1, R0 | ADD | R1, R0 | |
| LDI:32 | #_a, R12 | LDI:20 | #_a, R12 | |
| ST | R0, @R12 | ST | R0, @R12 | |
| 26 byte | 20byte | |||
2.2 除算(div step)命令使用時のオプション指定について
FRには除算のためにdiv step命令を持っています。しかしこの命令を使う場合は、36命令で1回の除算を行うことになるため、除算毎に72byte以上のコードが生成されることになります。
コンパイラは除算処理に対しデフォルトで実行時ライブラリを呼び出すコードを生成しますので、複数の除算命令が存在している場合は、デフォルト設定のままで、コードサイズが削減された出力が行われます。
しかしながら、速度優先最適化(-Kspeed)が指定された場合はdiv step命令を直接展開します。速度優先最適化指定時の除算処理によるコード増加が問題になる場合は、速度優先最適化を指定しない事を推奨致します。
| <Cソース> | <速度優先の場合> | <デフォルト> | ||
| a=b/c; | LDI:20 | #_b, R12 | LDI:20 | #_b, R12 |
| LD | @R12, R0 | LD | @R12, R4 | |
| LDI:20 | #_c, R12 | LDI:20 | #_c, R12 | |
| LD | @R12, R1 | LD | @R12, R5 | |
| MOV | R0, MDL | CALL20 | __divi, R12 | |
| DIV0S | R1 | LDI:20 | #_a, R12 | |
| DIV1 | R1 | ST | R4, @R12 | |
| DIV1 | 1 | |||
| DIV1 | R1 | |||
| DIV1 | R1 | |||
| IV1 | R1 | |||
| 74 byte | 20 byte(注) | |||
(注)divi関数が別途78byte作成されます。
(divi関数をライブラリとして使用すると、複数の除算命令実行時にはコード削減が見込めます。)
2.3 関数のスタック使用量が512byteを越えないように、ローカル変数の数を調整する
LD/ST命令ではFP相対アドレスが使用できます。しかしながら、16ビット命令長の制約から、指定可能なオフセットは最大-512~+508(4Byte型の場合)の範囲となっています。従って、512byteを越えるローカル変数領域を使用すると、スタックアドレス計算のための演算が増加しコードサイズ増加/アクセス効率が悪くなります。
そのため、関数のスタック使用量が512byteを越えないように、ローカル変数の数を調整して頂く事により、コードサイズの削減/アクセス効率の向上が図れます。
各関数のスタック使用量はSOFTUNE C/C++ Analyzerで確認することができます。
(注)ローカル変数の型が2byte型または1byte型の場合は、指定可能なオフセットはそれぞれ-256~254または-128~127となりますので、変数の型によって、効率良いコードの生成が可能なサイズは異なります。
| <Cソース> | <オフセットが-520の場合> (上記以上のサイズを扱う時) |
<オフセットが-4の場合> (上記以内のサイズを扱う時) |
||
| a=10; | LDI | #10, R0 | LDI | #10, R0 |
| LDI | #-520, R13 | ST | R0, @ (FP, -4) | |
| ST | R0, @ (R13, FP) | |||
| 8 byte | 4 byte | |||
2.4 符号付1byte/2byte型データの多用は避ける
FRアーキテクチャは符号付データのロード命令を持っていません。このため、符号付1byte/2byteデータをロードするときは、ロード後に符号拡張を行う必要があります。このため、符号付1byte/2byte型データを多用すると、符号なし型にくらべてコード量が増加します。
そのため、出来るだけ、符号なし型をご使用頂く事により、コードサイズの削減/アクセス効率の向上が図れます。
(注)Softune Compilerではchar型はunsigned char型として扱いますので、そのままお使いいただけます。
| <Cソース> | <signed char型の場合> | <char型の場合> | ||
| a=b+c; | LDI:20 | #_b, R12 | LDI:20 | #_b, R12 |
| LDUB | @R12, R0 | LDUB | @R12, R0 | |
| EXTSB | R0 | LDI:20 | #_c, R12 | |
| LDI:20 | #_c, R12 | LDUB | @R12, R1 | |
| LDUB | @R12, R1 | ADD | R1, R0 | |
| EXTSB | R1 | LDI:20 | #_a, R12 | |
| ADD | R1, R0 | STB | R0, @R12 | |
| LDI:20 | #_a, R12 | |||
| STB | R0, @R12 | |||
| 24 byte | 20 byte | |||
2.5 ループアンローリング最適化を抑止する
ループアンローリング最適化は、ループ回数を減らすことにより実行速度の向上をはかるものですが、オブジェクトサイズが増加する傾向があります。
スピードを意識する場合と、コードサイズを意識する場合のコード記述方法を検討する目安としてご検討下さい。
<アンローリング前>
for(i=0;i<6;i++){ a[i]=0;}
<アンローリング後>
for(i=0;i<6;i+3){
a[i]=0;
a[i+1]=0;
a[i+2]=0;
}
また、上記<アンローリング前>の記述でも、ループアンローリング抑止が指定されてないとコードサイズが大きくなりますので、サイズ優先最適化(-Ksize)またはループアンローリング抑止(-Knounroll)を指定して頂く事でコードサイズを意識したコンパイルが可能です。
<Cソース>
for(i=0;i<6;i++){a[i]=0;}
| <ループアンローリング最適化> | <アンローリング抑止> | ||||
| LDI:20 | #_a, R6 | LDI | #0, R4 | ||
| LDI | #0, R4 | L_26: | LDI | #0, R0 | |
| LDI | #2, R5 | LDI:20 | #_a, R13 | ||
| L_32: | LDI | #0, R0 | STB | R0, @ (R13, R4) | |
| MOV | R4, R13 | ADD | #1, R4 | ||
| STB | R0, @ (R13, R6) | CMP | #6, R4 | ||
| MOV | R6, R0 | BLT20 | L_26, R12 | ||
| ADD | R4, R0 | LDI | #0, R4 | ||
| LDI | #0, R1 | ||||
| LDI | #1, R13 | ||||
| STB | R1, @ (R13, R0) | ||||
| MOV | R6, R0 | ||||
| ADD | R4, R0 | ||||
| LDI | #0, R1 | ||||
| LDI | #2, R13 | ||||
| STB | R1, @ (R13, R0) | ||||
| ADD | #3, R4 | ||||
| ADD | #-1, R5 | ||||
| CMP | #1, R5 | ||||
| BGE20 | L_32, R12 | ||||
| 42 byte | 18 byte | ||||
2.6 インライン展開の必要性を検討する
インライン展開最適化は、Cソース上で定義された関数に対して、関数呼出しの代わりに呼び出し先関数の処理を展開します。展開される関数の処理が十分に小さい場合はインライン展開後のコードが小さくなる場合もありますが、通常は、オブジェクトサイズが増加する傾向があります。
オブジェクトサイズを重視する場合は、この最適化を実施しない方が良いと考えます。
(-xautoオプション、-xオプション、#pragma inline、inline型修飾子(C++のみ)を使用しないでください。)
<Cソース>
unsigned short ADD_sat16(unsigned short a, unsigned short b){
int tmp;
if(tmp=a+b)>0xffff) return 0xffff;
return (unsigned short)tmp;
}
unsigned short a,b,c,d,e,f;
func(){
a=ADD_sat16(b,c);
d=ADD_sat16(e,f);
}
| <インライン展開最適化> | <インライン最適化抑止> | ||||
| _func: | LDI:20 | #_b, R12 | _func: | ST | RP, @-SP |
| LDUH | @R12, R4 | LDI:20 | #_b, R12 | ||
| LDI:20 | #_c, R12 | LDUH | @R12, R4 | ||
| LDUH | @R12, R5 | LDI:20 | #_c, R12 | ||
| ADD | R5, R4 | LDUH | @R12, R5 | ||
| LDI | #65535, R0 | CALL20 | _ADD_sat16, R12 | ||
| CMP | R0, R4 | LDI:20 | #_a, R12 | ||
| BLE20 | L_32, R12 | STH | R4, @R12 | ||
| LDI | #65535, R4 | LDI:20 | #_e, R12 | ||
| BRA20 | L_28, R12 | LDUH | @R12, R4 | ||
| L_32: | EXTUH | R4 | LDI:20 | #_f, R12 | |
| L_28: | LDI:20 | #_a, R12 | LDUH | @R12, R5 | |
| STH | R4, @R12 | CALL20 | _ADD_sat16, R12 | ||
| LDI:20 | #_e, R12 | LDI:20 | #_d, R12 | ||
| LDUH | @R12, R4 | STH | R4, @R12 | ||
| LDI:20 | #_f, R12 | LD | @SP+, RP | ||
| LDUH | @R12, | R5 | RET | ||
| ADD | R5, R4 | ||||
| LDI | #65535, R0 | ||||
| CMP | R0, R4 | ||||
| BLE20 | L_36, R12 | ||||
| LDI | #65535, R4 | ||||
| BRA20 | L_34, R12 | ||||
| L_36: | EXTUH | R4 | |||
| L_34: | LDI:20 | #_d, R12 | |||
| STH | R4, @R12 | ||||
| RET | |||||
| 74 byte | 46 byte | ||||
ただし、引数がありコードサイズが小さい関数をstatic関数化、または#plagma inline指定することにより、コードサイズ削減することが可能になります。Softune C Analyzerの「インライン候補抽出機能」を利用することをお薦めします。
2.7 標準ライブラリインライン展開を抑止する
標準ライブラリインライン展開は、標準関数の動作を認識して、標準関数のインライン展開および同じ動作をする、より高速な標準関数への置き換えを行います。オブジェクトサイズを重視する場合は、この最適化を実施しないでください。標準ライブラリインライン展開抑止(-Knolib)を指定してください。
2.8 その他
参照回数の多い構造体メンバを先頭へ配置する
構造体メンバのアクセスでは 先頭アドレス+オフセットを計算して実際の配置アドレスを求めます。
先頭メンバは オフセット= 0 であるために計算が不要となります。静的なアクセス頻度が高いメンバがある場合、先頭に配置可能か検討してください。
構造体を返す関数のvoid化
構造体を返す関数では作業領域への構造体転送が発生します。代入先の構造体のアドレスを引数で渡し、直接代入することでvoid関数化することができます。
引数は4個以内にする
4個以内の引数はレジスタ渡しとなるためにスタックアクセスのためのコードが不要になり実行速度が改善されます。無駄に渡している引数がある場合は、削減するよう検討してください。
