Renesas GNU-RX 8.3.0 の最適化
Renesas GNU-RX の最適化は、通常の gcc (7.5.0) とは異なるようだー
より、深い最適化を行っている。
gcc-7.5.0 で、以下のプログラムをコンパイルすると・・
typedef device::PORT<device::PORT0, device::bitpos::B1> LED;
while(1) {
utils::delay::milli_second(250);
LED::P = 0;
utils::delay::milli_second(250);
LED::P = 1;
}
論理演算を行ったコードが生成される。
ffc0029c: cc 3e mov.b [r3], r14
ffc0029e: 75 42 fa mov.l #250, r2
ffc002a1: 75 2e fe and #-2, r14
ffc002a4: c3 3e mov.b r14, [r3]
ffc002bc: cc 3e mov.b [r3], r14
ffc002be: 65 1e or #1, r14
ffc002c0: c3 3e mov.b r14, [r3]
※中間に、「mov.l #250, r2」が挟まれてるのが多少痛いものの、まぁ普通だ。
Renesas GNU-RX では・・・
ffc00278: f0 38 bclr #0, [r3].b
ffc00294: f0 30 bset #0, [r3].b
ビット操作命令になっている。
RAYTRACE_sample が、361ms から 351ms に時間短縮したのも、他、細かい最適化によるものと思われる。
ルネサス社が魔改造したコンパイラは、高性能なのだと思われる。
※何故、gcc のオリジナルコードにマージしないのか?、疑問は残る・・・
Renesas GNU-RX 8.3.0 のオプション
倍精度浮動小数点命令を生成するには、コアが「RXv3」でなおかつ、「dfpu」を有効にすれば良いらしい。
※gcc のソースコードで、RX マイコンに関係する部分を少し読んでみた。
-misa=v3 -mdfpu
なので、上記オプションを使って、簡単なコードで調べてみた。
double a = 0.0;
while(1) {
utils::delay::milli_second(250);
LED::P = 0;
utils::delay::milli_second(250);
LED::P = 1;
a += 0.1;
utils::format("%5.4f\n") % static_cast<float>(a);
}
アセンブルリストを見てみると、確かに「倍精度浮動小数点命令」が生成されている、「a + 0.1;」の部分
ffc003d2: fc c9 08 12 10 dmov.D 72[r0], dr1
ffc003d7: f9 03 00 9a 99 99 99 dmov.L #0x9999999a, drl0
ffc003de: f9 03 02 99 99 b9 3f dmov.L #0x3fb99999, drh0
ffc003e5: 76 90 00 11 dadd dr1, dr0, dr1
ffc003e9: fc 79 08 12 10 dmov.D dr1, 72[r0]
※ 64 ビットの IEEE-754 で定数 0.1 の表現は、0x3fb9'9999'9999'999a となる。
※ 64 ビットの定数をレジスターにロードするコストは大きい・・・
ちゃんと、倍精度の命令を生成しているので、RAYTRACE_sample を走らせてみた。
・・・逆に遅くなる。
調べると、dfpu を指定しない場合、常に32ビットで演算されていたものが、dfpu の指定で、部分的に64ビットの演算が行われる為のようだ。
実際にプログラムで利用する際は、細心の注意が必要だと思える・・・
※インテル CPU の場合、double を float にして計算すると、逆に遅くなるが、それとは対照的だ・・
でも、原理は理解は出来る。
それでも、64ビットの計算を、専用命令で行えるのは、ソフトエミュレーションに比べて格段に速いと思われるので、倍精度命令が生成される事実は心強い。
TFU についての調査
TFU については、仕様が公開されていないので、どのような物か、良く判らない・・・
gcc のソースコード「gcc/config/rx/rx.opt」を見ると・・・
EnumValue
Enum(rx_tfu_types) String(intrinsic) Value(RX_INTRINSIC)
EnumValue
Enum(rx_tfu_types) String(intrinsic,mathlib) Value(RX_MATHLIB)
とあるので、
-mtfu=intrinsic
-mtfu=intrinsic,mathlib
のどちらかを設定すれば良さそうだと判る。
「gcc/config/rx/rx.c」で、
if (TARGET_TFU)
{
ADD_RX_TFU_BUILTIN (INIT, "__init_tfu", void);
ADD_RX_BUILTIN3 (SINCOSF, "sincosf", void, float, float_ptr, float_ptr);
ADD_RX_BUILTIN4 (ATAN2HYPOTF, "atan2hypotf", void, float, float, float_ptr, float_ptr);
if (rx_tfu_type == RX_MATHLIB)
{
ADD_RX_BUILTIN1 (SINF, "sinf", float, float);
ADD_RX_BUILTIN1 (COSF, "cosf", float, float);
ADD_RX_BUILTIN2 (ATAN2F, "atan2f", float, float, float);
ADD_RX_BUILTIN2 (HYPOTF, "hypotf", float, float, float);
}
}
となっている、
初期化の関数と思われる「__init_tfu」は、勝手に呼ぶコードが埋め込まれるのか不明だ。
※自動的に ctor に積まれるのかもしれない・・・
上記関数が演算器で演算されるものと思われるが、どのくらい効果があるのか不明・・・
また、現段階では、演算器が使われているかも不明で、何か、他に設定する事があるのかもとも思う。
とりあえず、TFU に関しては、調査を続ける。