富士通

FRファミリー一覧ページへ

開発にあたってのノウハウ

2. コード効率を上げるには

2.1 20bitアドレスモード設定を利用する

2.2 除算(div step)命令使用時のオプション指定について

2.3 関数のスタック使用量が512byteを越えないように、ローカル変数の数を調整する

2.4 符号付1byte/2byte型データの多用は避ける

2.5 ループアンローリング最適化を抑止する

2.6 インライン展開の必要性を検討する

2.7 標準ライブラリインライン展開を抑止する

2.8 その他


2.1 20bitアドレスモード設定を利用する

FRでは、演算の際は通常、以下の3ステップで処理を行います。

  1. メモリアドレスのレジスタ設定
  2. データのレジスタへのロード
  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個以内の引数はレジスタ渡しとなるためにスタックアクセスのためのコードが不要になり実行速度が改善されます。無駄に渡している引数がある場合は、削減するよう検討してください。