Renesas



Section1
Section2
Section3
Section4
Section5
Section6




ここから先はRX用Cコンパイラのオプションと、そのオプションを使った効率アップのワンポイントアドバイスを紹介します。

最初は、大域変数に対する最適化オプションについて紹介します。RXに限らず、大域変数はC言語の言語仕様上、必ずメモリに割り付きます。RXの場合、メモリはレジスタ間接のアドレッシングモードでアクセスすることになりますから、大域変数を操作すると、その都度、汎用レジスタにアドレスをロードすると言う命令が必要となります。RXはメモリ空間が4GBあるため、このアドレスロードの命令中にあるアドレス情報は、全て32ビットに展開されるため、機械語の長い命令となってしまいます。そこで、この32ビットのアドレスが含まれる機械語を短くするのが、大域変数に対する最適化オプションです。

紹介する大域変数に対する最適化オプションはSuperH用Cコンパイラのものを継承しています。SuperHファミリのユーザの方は違和感なく、お使い頂けるものだと思います。大域変数に対する最適化、言い換えると外部変数アクセスの最適化オプションとは大域変数を構造体化し、構造体の先頭アドレスだけを汎用レジスタにロードして、各メンバは相対アクセスで操作することにより、コード効率の向上を図ります。その指定には「モジュール内」と「モジュール間」の2つがあります。「モジュール内」の指定はファイル内に変数定義がある大域変数だけを構造体化します。「モジュール間」の指定はファイル間に跨って、全ての大域変数を構造体化します。外部変数アクセスの最適化オプションはツールチェインのコンパイラのタブ、カテゴリのドロップダウン・メニューは最適化を選択し、外部変数アクセス最適化のドロップダウン・メニューで選択します。なお、「モジュール間」を選択した場合、コンパイラと最適化リンカは2回動作し、両者が連携を取って最適化を実施します。その理由はファイル間に跨って構造体化を行うためには一度、最適化リンカを起動して、大域変数のアドレスを決定しなければならないからです。このため、「モジュール間」を選択した場合、「最適化リンカにもオプションを設定しました」というワーニングメッセージが出力されますが、これは気にせずに「OK」してください。

それでは最適化オプションの効果を見てみましょう。プログラムではファイル内で宣言したa、b、cと外部参照しているx、y、z、これらについて演算を行っています。「モジュール内」の場合、最適化の効果はファイル内で宣言されたa、b、cしか効果はありません。更に変数サイズが同じものだけを構造体化します。結果、この例では変数aとbだけが構造体化の対象となります。この部分が変数aからの相対アドレスを利用した変数bの操作命令です。まず、変数aのアドレスを汎用レジスタに入れて、変数bは変数aからの相対アドレスで操作することで本来ならば32ビット必要であった変数bのアドレス情報が削減できます。ただし、「モジュール内」の場合、最適化の効果は殆どありません。同一ファイルに変数定義のあるもの、しかも同一サイズの変数しか最適化の効果がありません。

これに対して、「モジュール間」の場合、変数の定義場所を問わず、ファイル間に跨って構造体化を行うことが可能です。ただし、「モジュール内」の時と同じようにサイズの異なる変数は構造体化されません。結果、この例では変数a、b、x、yが、また変数c、zが構造体化の対象となります。この部分が変数aからの相対アドレスを利用した変数b、x、yの操作命令であり、この部分が変数cからの相対アドレスを利用した変数zの操作命令です。このように「モジュール間」の場合、外部参照に対しても効果が出ますから、外部変数アクセスの最適化を実施しなかった場合や「モジュール内」の最適化を実施した場合より、効率良く大域変数が操作可能となります。

今度はベースレジスタを使ったコード効率の向上について紹介します。RX用Cコンパイラでは、汎用レジスタR8からR13のいずれか1つをベースレジスタに指定することができ、そこからの相対値を用いてデータにアクセスすることでコード効率の向上が図れます。ベースレジスタは、ROM領域やRAM領域の変数に対しても利用可能ですが、変数領域ならば、先に紹介した外部変数アクセスの最適化オプションがあるため、変数としては宣言せず、#defineを使って操作するI/O領域に対して利用することを推奨しています。ベースレジスタのオプションはツールチェインのCPUタブ、ベースレジスタの項目で指定します。I/O領域の場合、ベースアドレスの設定番地と、それに割り当てる汎用レジスタを選択します。

それではベースレジスタを使用した場合と使用しない場合のコード効率の違いを見てみましょう。先にベースアドレスを使用しなかった場合の例から見てみます。これはTPUをPWMモードで初期化したプログラムです。これをオプション設定せずにコンパイルするとアクセスする周辺機能の先頭番地を汎用レジスタに設定し、そこから距離の近いレジスタは8ビット相対アドレス、距離の遠いレジスタは16ビットの相対アドレスで操作する結果となります。

今度は先の例に対してベースレジスタのオプションを設定した時のコンパイル結果を見てみましょう。これはプログラムの中で一番多く操作しているTPUのチャネル0の先頭アドレス、具体的には8万8千百ヘキサ番地に対し、汎用レジスタのR13をベースアドレスとして設定したものです。見て分かるように設定したベースアドレスから近い距離にあるレジスタの操作は、関数毎にベースアドレスをロードすることなく、8ビット相対アドレスとなり、コード効率が向上します。ただし、全てのレジスタの操作が8ビット相対アドレスとなる訳ではありません。ベースアドレスから距離の遠いレジスタは16ビット相対アドレスとなったり、場合によってはこの例のようにベースレジスタを使わない時もあります。そこで次頁では、ベースレジスタを使用する際の注意事項を紹介します。

まず、ベースレジスタに指定した汎用レジスタに対するベースアドレスの設定はエントリ関数の先頭で1度だけ実施されます。システム実行中の再設定、言い換えればベースアドレスの変更は許されません。従って、ベースレジスタに設定するベースアドレスはシステムの中で最も使用頻度の高い領域に設定することが効率アップに繋がります。ベースレジスタを用いたデータアクセスには8ビット相対アドレスと16ビット相対アドレスがあり、効率の良い8ビット相対アドレスは、ベースアドレスから256個分までのディスプレースメントに限られます。ただし、ディスプレースメントは操作するデータのサイズに依存し、自動的にスケーリングされますから、1バイトデータであれば256バイトまで、2バイトデータであれば512バイトまで、4バイトデータであれば1024バイトまで8ビット相対アドレスで操作可能です。これらを超えてしまう場合には16ビット相対アドレッシングになります。更にベースアドレスを用いたデータのアクセスはベースアドレスから番地の大きい方のみがアクセスの対象となり、番地の小さい方のデータには効果がありません。これらの内容に注意を払いながら、システムの中で最も使用頻度の高い領域にベースレジスタを設定してください。

今度は最適化方法について紹介します。RXの最適化にはレベルがあり、ツールチェインのコンパイラのタブ、カテゴリのドロップダウン・メニューは最適化を選択し、最適化レベルのドロップダウン・メニューでレベルを指定します。そして、このレベルと合わせてサイズ優先で最適化を実施するか、スピード優先で最適化を実施するか、最適化方法のドロップダウン・メニューで選択可能です。最適化レベル2の時のサイズ優先、またはスピード優先を選んだ場合に、どのような最適化が実施されるかが、この表になります。これらの最適化オプションは、別途個別に設定することができますが、最適化レベル2を選択した場合、ループ展開、インライン展開、定数除算の乗算化についてはスピード優先、サイズ優先で効果が異なる結果となります。

なお、先程の表は最適化レベル2のものですが、それ以外にも最適化レベルはMAX、1または0があり、どの最適化レベルを選択したかにより最適化項目が変化します。他の最適化レベルに関しては、こちらの表を参考にしてください。いづれにしてもスピード優先にすればROMサイズは大きくなる傾向に、逆にサイズ優先にすればスピードは遅くなる傾向になります。従いまして、スピードを優先するか、サイズを優先するかはシステムの都合に応じて選択してください。以降の紹介では各レベルにおいて、スピード優先を選択した場合とサイズ優先を選択した場合で効果の変化するループ展開、インライン展開、定数除算の乗算化について詳しく見ていきます。

初めはループ展開です。ループ展開の最適化は繰り返し実行される文を展開し、ループ回数を削減します。このプログラムでは配列要素の変数retへの足し込みを1要素ずつ100回の繰り返しで行われます。これをループ展開により最適化すると1回のループでi番目とi+1番目の要素を変数retに足し込むことでループ回数を半分の50回に削減します。このようにループ回数を100回から50回に削減することでスピードが速くなります。ただし、1回の処理における足し算命令は増えますから、プログラムサイズは大きくなります。

このループ展開の最適化は最適化レベルとは別にカスタム設定が可能です。設定はツールチェインのコンパイラのタブ、カテゴリのドロップダウン・メニューは最適化を選択し、詳細ボタンを押します。そうすると詳細のダイアログが表示されますから、その他のタブにおけるループ展開でカスタムを選択し、展開回数を数値で指定してください。最適化レベル2を選択した場合、ループ展開は2回までしか行いませんが、このカスタムを選ぶと最高32回までの展開が可能です。これが先の例でそれぞれの回数を選択したときのサイズとスピードの比較です。この時、ある程度まではスピードが上がりますが、一定以上展開を増やすとそれほどスピードは上がらず、逆にサイズばかりが増えてしまうので逆効果になることがあります。従って、全部を展開すれば良いと言う訳ではなく、今回の例ですと10回くらいが最適値ということになります。

今度はインライン展開について紹介します。インライン展開の最適化とは小規模な関数を呼び出し元に埋め込むものです。最適化レベル2では関数サイズが100%まで、言い換えれば関数サイズが100%の2倍になるまではインライン展開する、という意味になります。それではインライン展開の具体例を見てみましょう。例えば、このようなswapという関数があり、main関数からこのswap関数を2回呼び出していたとします。通常は記述された通り、swap関数を呼び出して処理を実現しますが、インライン展開が実施されると、このようにswap関数の処理内容がmain関数に埋め込まれる、つまりインライン展開された形式となります。この結果、インライン展開を実施すると、関数呼び出しを行わない分、スピードは速くなりますが、処理内容が呼んだ回数分、埋め込まれるため、サイズは増える傾向にあります。

このインライン展開も最適化レベルとは別にカスタム設定が可能です。設定はツールチェインのコンパイラのタブ、カテゴリのドロップダウン・メニューは最適化を選択し、詳細ボタンを押します。そうすると詳細のダイアログが表示されますから、表示されたダイアログのインライン展開のタブにおける自動インライン展開でカスタムを選択し、プログラムサイズが何%になるまで展開するかを設定してください。関数サイズが最高で65,535%大きくなるところまでインライン展開を指定することができます。

最後に除算を乗算化する最適化を紹介します。どのようなマイコンでも割り算命令はサイクル数が掛かります。除算を行うのであれば、乗算の方が多少なりともサイクル数を削減することができます。そこで、定数による除算を乗算化する最適化が用意されています。例えば、ある変数を10で割った商を求めるのであれば、目的の変数に0.1を乗算しても結果は同じです。もし、この最適化を実施した場合、サイクル数の掛かる除算命令を使わず、固定小数点の0.1を乗算するコンパイル結果となります。この結果、サイクル数の掛かる除算命令を使わないため、スピードは向上します。ただし、固定小数点を使用して乗算を行うため、サイズは大きくなります。

この定数除算の乗算化も最適化レベルとは別に個別に設定することができます。設定はツールチェインのコンパイラのタブ、カテゴリのドロップダウン・メニューは最適化を選択し、詳細ボタンを押します。そうすると詳細のダイアログが表示されますから、表示されたダイアログのその他のタブにおける定数除算/剰余算のドロップダウン・メニューで選択してください。

最適化をいくつか紹介しましたが、ここでは最適化レベルに対する注意事項を紹介します。最適化のオプションは非常に多いので、その設定の目安と考えて頂ければ幸いです。まずは最適化レベルです。最適化レベルとしてMAXを指定した場合、ループ展開も最大となり、インライン展開も結構な数を埋め込んでしまいます。結果、スピードは確かに上がるのですが、コード効率がかなり悪くなってしまう場合があります。従って、最適化レベルはデフォルトのレベル2として、これらの最適化を個別オプションで設定し、どれくらいの効果が出るのかを評価しながら設定することを推奨します。また、それとは別に一般関数と割り込み関数の両者から利用する大域変数には、必ずvolatile型修飾子を指定することをお奨めします。

例えば、このプログラムのように大域変数sumやiをループ内で演算している場合、毎回、大域変数、つまりメモリに対してアクセスを行うと効率が悪くなります。そこでコンパイラは最適化により、ループ内での演算は汎用レジスタを用いて行い、最終的に演算が終わった後にその汎用レジスタの結果を大域変数に書き込むという最適化を行うことでメモリアクセスの回数を減らし、効率アップを図っています。このような最適化の内容はmain関数以下、一般に動作する関数だけで見れば問題はないのですが、割り込みによって突発的に動作する関数が存在していた場合、問題となります。その理由はfor文の処理を実行中に割り込みが発生し、割込み関数から演算の途中結果を参照する目的で大域変数sumやiを参照してもsumやiには演算途中の結果が格納されていないからです。このような現象を防ぐためには一般関数と割り込み関数の両者から利用する大域変数には必ずvolatile型修飾子をつけて宣言してください。

ここからは最適化の話ではなく、組み込み関数について紹介します。RXのコンパイラでは、内部レジスタの操作やマイコンの持つ効率の良い命令を用いたプログラミングをC言語で記述するために組み込み関数を用意しています。最大値、最小値比較、エンディアンの並べ替え、データ交換など、マイコンの持つ特殊な命令の使用をC言語で記述する場合は組み込み関数を使ってください。

また、内部レジスタの設定・参照を行う組み込み関数も用意されています。例えば、システムで割込みを使用する場合はステータスワードの値を設定したり、高速割り込みで紹介した高速割り込みベクタレジスタに関数の先頭アドレスを設定する等、内部レジスタの設定・参照を行うための組み込み関数も用意されています。

set_psw関数を用いた割り込み許可の設定とset_fintv関数を用いた高速割り込みの設定例です。それぞれ展開結果がこのようになります。このように組み込み関数を呼び出すとステータスワードの変更や高速割り込みベクタレジスタの設定が直接RXの命令コードに展開されます。

最後は移植性に関するオプションの紹介です。M16CやH8からRXに移行する際に、プログラムの移植性という面で、なるべくコードを変えずに、そのまま移植できるようにするためのオプションをいくつか用意しています。これらはANSI規格で定義されていない項目について、それぞれのコンパイラの扱いについてまとめたものです。これらの項目はANSI規格で定義されていないため、コンパイラごとに扱いが異なります。例えば、RXのコンパイラでは符号を指定しないchar型は符号なしchar型として扱いますが、H8やSHのコンパイラでは符号を指定しないchar型は符号ありchar型として扱います。この結果、char型の符号に関してはM16Cからの移植であれば問題ありませんが、H8やSHから移植する際には符号を指定しないchar型に対して符号ありになるようにソースプログラムを書き換えなければなりません。

そこでRXのCコンパイラではANSI規格で未定義の項目に対しては、オプションによって差異を吸収することが可能となっています。設定場所は、ツールチェインのCPUタブで行います。この画面ではエンディアンだけとなっていますが、詳細ボタンを押すと、残りのdouble型の精度やchar型の符号の選択等が指定できるようになっています。これらを必要に応じてチェックしてビルドを行えばコンパイラが自動的に言語仕様の差異を吸収しますので、言語仕様の差異に関わるソースプログラムの変更は不要となっています。