GR-SAKURA
GR-KURUMI
GR-COTTON
GR-CITRUS
GR-PEACH
GR-KAEDE
GR-ADZUKI
GR-LYCHEE
GR-ROSE
GR-MANGO(*)
SNShield
Web Compiler
IDE for GR
TOPPERS関連
女子美コラボ
その他
※プロデューサミーティング中
作り方使い方資料
イベント関連
作品記事
体験記事
ライブラリ
ツール
その他・過去ファイル
GR-KURUMI について、web コンパイラで使用している gcc の出力するコードの品質がヒドいと以前から感じていたものの、実際どんなもんだかよくわかってなかったので他のコンパイラと比べてみた。
今回確認に使用したコンパイラは下記の4本である。
GNURL78 v13.01 Windows Tool Chain (ELF) MP1 は現在 web コンパイラで使用されているものであり、GNURL78 v13.02 Windows Tool Chain (ELF) MP1 は RL78 用にビルドされた gcc の最新版である。CA78K0R v1.60 はルネサス純正の開発ツール CubeSuite+ に同梱されているコンパイラである。IAR C/C++ Compiler for Renesas RL78 1.30.5 は IARシステムズ より販売されている RL78 用統合開発環境 ルネサス RL78用 IAR Embedded Workbench に同梱されている同社製コンパイラである。
評価に使用したプログラムは、以前 『円周率対決』 で使用したものを gcc と CA78K0R と IAR C/C++ Compiler のそれぞれで使用できるよう改変したものである。下記のプログラムは、円周率を小数点以下 1000 桁求め、その結果と所要時間を GR-KURUMI の IO7 ピン(←重要)に、9600bps のシリアル通信にて出力する。
使用したプログラムを以下に記す。
gr_sketch.c:
#if defined(__GNUC__) #include <iodefine.h> #include <iodefine_ext.h> #define ei() __asm __volatile("EI;") #define _SAU0EN PER0.BIT.sau0en #define _SMR02 SMR02.smr02 #define _SCR02 SCR02.scr02 #define _SPS0 SPS0.sps0 #define _SDR02 SDR02.sdr02 #define _SOE02 SOE0.BIT.bit2 #define _SS0 SS0.ss0 #define _P0 P0.p0 #define _PM0 PM0.pm0 #define _SSR02L SSR02L.ssr02l #define _TXD1 TXD1.txd1 #define _TAU0EN PER0.BIT.tau0en #define _TPS0 TPS0.tps0 #define _TMR00 TMR00.tmr00 #define _TDR00 TDR00.tdr00 #define _TS0Lbit0 TS0.BIT.bit0 #define _TMIF00 IF1.BIT.tmif00 #define _TMMK00 MK1.BIT.tmmk00 #define _TCR00 TCR00.tcr00 const unsigned char Option_Bytes[] __attribute__ ((section (".opt"))) = { 0xef, 0xff, 0xe8, 0x85 }; const unsigned char Security_Id[] __attribute__ ((section (".security_id"))) = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; void inttm00() __attribute__ ((interrupt)); const void* Vectors[] __attribute__ ((section (".ivec"))) = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, inttm00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; void HardwareSetup() {} #elif defined(__CA78K0R__) #pragma sfr #pragma ei #define ei() EI() #define _SAU0EN SAU0EN #define _SMR02 SMR02 #define _SCR02 SCR02 #define _SPS0 SPS0 #define _SDR02 SDR02 #define _SOE02 SOE0L.2 #define _SS0 SS0 #define _P0 P0 #define _PM0 PM0 #define _SSR02L SSR02L #define _TXD1 TXD1 #define _TAU0EN TAU0EN #define _TPS0 TPS0 #define _TMR00 TMR00 #define _TDR00 TDR00 #define _TS0Lbit0 TS0L.0 #define _TMIF00 TMIF00 #define _TMMK00 TMMK00 #define _TCR00 TCR00 #pragma interrupt INTTM00 inttm00 #define lrintf(f) ((int)(f)) #elif defined(__ICCRL78__) #include <iorl78_1.h> #include <iorl78_1_ext.h> #include <intrinsics.h> #define ei() __enable_interrupt() #define _SAU0EN SAU0EN #define _SMR02 SMR02 #define _SCR02 SCR02 #define _SPS0 SPS0 #define _SDR02 SDR02 #define _SOE02 SOE0L_bit.no2 #define _SS0 SS0 #define _P0 P0 #define _PM0 PM0 #define _SSR02L SSR02L #define _TXD1 TXD1 #define _TAU0EN TAU0EN #define _TPS0 TPS0 #define _TMR00 TMR00 #define _TDR00 TDR00 #define _TS0Lbit0 TS0L_bit.no0 #define _TMIF00 TMIF00 #define _TMMK00 TMMK00 #define _TCR00 TCR00 #pragma location = "OPTBYTE" __root const unsigned char Option_Bytes[] = { 0xef, 0xff, 0xe8, 0x85 }; #pragma location = "SECUID" __root const unsigned char Security_Id[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #endif typedef signed char int8_t; typedef unsigned char uint8_t; typedef signed short int16_t; typedef unsigned short uint16_t; typedef signed long int32_t; typedef unsigned long uint32_t; #define CPUCLOCK 32000000UL void uartInit(uint32_t baud) { uint16_t sps; uint16_t sdr; for (sps = 0; sps <= 15; sps++) { sdr = (uint16_t)((CPUCLOCK / (1 << sps) + baud) / (baud * 2)); if (sdr <= 128) { sdr = (sdr - 1) << 9; break; } } _SAU0EN = 1; // シリアル・アレイ・ユニット1の入力クロック供給 _SMR02 = 0x0022U; // UARTモード _SCR02 = 0x8097U; // LSBファースト、STOPビット1、データ8ビット、送信のみを行う _SPS0 = sps; // CKm0 シリアルクロック選択 _SDR02 = sdr; // 動作クロックの分周による転送クロック設定 _SOE02 = 1; // シリアル出力許可 _SS0 |= 0x0004; // シリアル・チャネル開始 _P0 |= 0x01; // 1を出力 _PM0 &= ~0x01; // 出力モード } void uartPutc(char c) { while (_SSR02L & 0x20) { ; } _TXD1 = c; } void uartPuts(const char* s) { char c; while ((c = *s++) != '\0') { uartPutc(c); } } void uartPutn(uint32_t n) { if (n >= 10) { uartPutn(n / 10); } uartPutc('0' + (uint8_t)(n % 10)); } static volatile uint32_t millisCount; #if defined(__CA78K0R__) __interrupt #elif defined(__ICCRL78__) #pragma vector = INTTM00_vect __interrupt #endif void inttm00() { millisCount++; } void timerInit() { millisCount = 0; _TAU0EN = 1; // タイマ・アレイ・ユニット0の入力クロック供給 _TPS0 = 0x0000; // CKm0 = fCLK = 32MHz _TMR00 = 0x0000; // インターバル・タイマ・モード _TDR00 = 32000 - 1; // 割り込み周期 = CKm0 / 32000 = 1KHz _TMIF00 = 0; // 割り込み要求クリア _TMMK00 = 0; // 割り込み処理許可 _TS0Lbit0 = 1; // タイマ・チャネル0開始 } uint32_t millis() { return millisCount; } #define DIGITS 1000 #define BUFBITS ((int)(/*log(10)/log(2)*/3.32192809489 * DIGITS) + 1) static uint8_t buf[(BUFBITS + 7) / 8]; static uint8_t pi_dig16(unsigned n); int main() { int i; int j; int c; uint32_t start; uint32_t time; ei(); uartInit(9600); timerInit(); start = millis(); for (i = 0; i < ((BUFBITS + 3) / 4); i++) { if ((i & 1) == 0) { buf[i / 2] = pi_dig16(i) << 4; } else { buf[i / 2] |= pi_dig16(i); } } time = millis() - start; uartPuts("PI = 3.\r\n"); c = 0; for (i = 0; i < DIGITS; i++) { for (j = (BUFBITS + 7) / 8 - 1; j >= 0; j--) { c += 10 * (int)buf[j]; buf[j] = (uint8_t)c; c /= 256; } uartPutn(c); if ((i + 1) == DIGITS || i % 50 == 49) { uartPuts("\r\n"); } else if (i % 10 == 9) { uartPuts(" "); } } uartPuts("\r\n"); uartPutn(time); uartPuts("msec.\r\n"); return 0; } /* このコメントより下に、 http://www.mikrocontroller.net/articles/4000_Stellen_von_Pi_mit_ATtiny2313 で公開されてる pi.c の内容をコピペし、 stdint.h をインクルードしている行と 関数 cput() と main() を削除する。 CA78K0R は C99 に対応してないっぽいので、for 文のループ変数の宣言をブロックの頭に移動する。 */
結果は以下の通り。
プログラム中のコードセクションの gr_sketch.c から生成された部分のサイズ(バイト)
以下、所感。
テストに使用したプログラムのビルド方法が抜けていたので以下に解説する。
web コンパイラ:
今回テストに使用したプログラムは、下記の手順でお手軽に動作をご確認いただけます。
ローカルビルド:
GNURL78 v13.01 Windows Tool Chain (ELF) MP1 や GNURL78 v13.02 Windows Tool Chain (ELF) MP1 を使用して、最適化の条件を変えて動作確認を行ったりするには web コンパイラではなく、ローカルビルド環境が必要となる。
分かってる人しかやらないと思うので説明は割愛。
CubeSuite+ を使用してのビルド:
CA78K0R v1.60 が同根されている統合環境である CubeSuite+ を使用する。
分かってる人しか(以下略
IAR Embedded Workbench:
スタックサイズを初期値の 128 バイトから 256 バイト等のもう少し大きい値にしないと上手く動作しない模様。
Fujitaさん、いつもながらにありがとうございます。大変ありがたいです。内部も前回円周率対決の時点から(正確には三角関数の演算やビット操作あたりから)、徐々に動いてます。とはいえやはり純正でもこの結果というのは、今回改めて外の方にしていただいたのが非常に大きいです。お忙しい中での実施とレポート、本当に感謝しております。
Okamiya 様、
GNURL78Toolchain と CA78K0R の両方について、改善の一助にでもなれば嬉しいです。
所感のなかで、
CA78K0R を使用していて、uint8_t の変数と整数リテラルとの演算を行うと uint8_t のまま行うようなのだけど、これって C 言語の仕様的に正しいのだろうか? 整数リテラルの型に合わせて int 等に昇格してから演算されると思ったんだが違ったかな?
と書きましたが、確認したところ、これは CubeSuite+ が標準で有効化している CA78K0R の最適化機能「char型演算を符号拡張しない」によるものと分かりました。
unsigned char uc = 128; int i = uc + 128;
とすると、おそらくは標準化されてる C 言語のどの規格に従っても、uc の値を int に昇格して uc + 128 を int の精度で計算をするので i には 256 が入りますが、CA78K0R ではこの最適化が有効になっていると unsigned char として計算を行うため i には 0 が入ります。
組み込み用途にコードサイズを小さくする目的でこのような機能が提供されていることは理解しますが、標準で有効化されてる(=C言語の規格からは外れる)のはどうかという気がしました。
勉強になります・・・CubeSuite+, IARまで環境を構築して・・・すごすぎ。意外とCA78K0Rが頑張ってる感想を持ちました
どうも float の演算部分が実行速度に大きく影響するみたいだったので、float で演算していた箇所を固定小数点(=整数)演算に変更し、各コンパイラで実行してみた。
/* 先のプログラムの mod_pow16() より下をガーっと消して以下の内容をコピペする */ #define DecimalPoint 24 static int32_t tame (int32_t s) { int8_t si = (int8_t)(s >> DecimalPoint); if (si <= -2 || si >= 2) s -= ((int32_t)si << DecimalPoint); return s; } static int32_t div_a (unsigned a, unsigned b) { unsigned i; int32_t r = 0; for (i = 0; i < DecimalPoint; i++) { a <<= 1; r <<= 1; if (a >= b) { a -= b; r |= 1; } } return r; } static int32_t sigma_a (unsigned n, uint8_t j) { int32_t s = 0; unsigned k; for (k = n-1; k+1 != 0; k--) { unsigned j_8k = j + 8*k; s += div_a (mod_pow16 (n-k, j_8k), j_8k); s = tame (s); } return s; } static int32_t div_b (unsigned a, unsigned b) { unsigned acc = 0x0001; int32_t r = 0; do { acc <<= 1; r <<= 1; if (acc >= b) { acc -= b; r |= 1; } } while (--a); return r; } static int32_t sigma_b (unsigned n, uint8_t j) { int32_t s = 0; unsigned i; for (i = DecimalPoint; i > 0; i -= 4) { s += div_b (i, j + 8*n); n++; } return s; } int32_t sigma (unsigned n, uint8_t j) { return sigma_a (n, j) + sigma_b (n, j); } int32_t pi_n (unsigned n) { return 4 * sigma(n, 1) - 2 * sigma(n, 4) - sigma(n, 5) - sigma(n, 6); } static uint8_t pi_dig16 (unsigned n) { return 15 & (pi_n (n) >> (DecimalPoint - 4)); }
んーーーー(ニヤニヤ)、確かにおっしゃる通りの結果ですね。GCCもやっぱり洗練された部分があるんですね。今回は課題を洗い出していただく形になりましたが、Fujitaさん、ルネナイ4来ていただけますかね。。特別なんとか賞を検討させてください。
gcc の実行時間の成績についてはハッキリ言って困惑しています。ちょっと出力コードを覗いてみましたが、ちょっと酷いなと思ったのが、div_a() のなかの r |= 1; が
mov a, [sp] or a, #1 mov [sp+4], a mov a, [sp+1] mov [sp+5], a mov a, [sp+2] mov [sp+6], a mov a, [sp+3] mov [sp+7], a movw ax, [sp+4] movw [sp], ax movw ax, [sp+6] movw [sp+2], ax
というコードになってて頭抱えました。そんなこんながあちこちにあるようでコードも肥大してると思うのですが、対して IAR は出力コードは面倒なので見てませんがコードサイズが小さい分それだけ無駄のないものになってると想像するのですが、なんで gcc の方が実行時間で勝ってるのか不思議でなりません。アルゴリズム的にボトルネットとなる箇所が奇跡的に効率の良いコードになってるとかなんでしょうか。まだよく確認してみないとわかりません。あ、static な関数は積極的にインライン展開してくれてるのは gcc 流石だなというのはありました(その分追い難い)。gcc も x86 なんかだと手を入れるべきところがちょっと見当たらないくらいのコード吐いたりしますが、RL78 はまだ全然そんな完成度ではないので、今回のテストで RL78 のコンパイラは gcc に限らず各社まだ改善すべき点があるのだなあ、という印象でした。
ルネサスナイト4ですが、今考えてるものが間に合えば積極的に参加したいと思っているのですが、まあ、前回も見学での参加だったので今回も発表できるもんが用意できなくても行きたいとは思っております。
プログラム中、mod_mul() という関数がもっとも呼び出される回数が高いっぽいのですが、この関数を試しにインラインアセンブラを使用して手動による最適化を試してみました。
この関数は GNURL78 v13.02 Windows Tool Chain (ELF) MP1 でコンパイルすると 101バイトのサイズになるのですが、今回試しで書いたものは
static unsigned mod_mul (unsigned a, unsigned b, unsigned n); __asm ( "rb0bc = 0xffefa \n" "rb0de = 0xffefc \n" "rb1ax = 0xffef0 \n" " .section .text \n" " .global _mod_mul \n" " .type _mod_mul, @function \n" "_mod_mul: \n" " movw ax, [sp+6] \n" " movw bc, ax \n" " movw ax, [sp+8] \n" " movw de, ax \n" " movw ax, [sp+4] \n" " movw rb1ax, #0 \n" "0: \n" " shrw ax, 1 \n" " bnc $1f \n" " sel rb1 \n" " addw ax, rb0bc \n" " subw ax, rb0de \n" " sknc \n" " addw ax, rb0de \n" " sel rb0 \n" "1: \n" " cmpw ax, #0 \n" " sknz \n" " ret \n" " xchw ax, bc \n" " addw ax, ax \n" " subw ax, de \n" " sknc \n" " addw ax, de \n" " xchw ax, bc \n" " br $0b \n" );
43バイトのサイズとなり、サイズは半分以下となりました。
実行時間の成績も、この関数の差し替えだけで 167,533(ミリ秒) → 124,365(ミリ秒) と、約 25.8%向上しました。
ひとつの関数だけでなく、円周率計算部分全体をアセンブラでコーディングし直せば実行時間の成績も更に(40数%くらいまでいけそうな感じ※)向上すると思われます。
いまどきの x86 や ARM なんか用のこなれた最適化コンパイラではこの程度の小さな関数について人手による改善の余地はここまで大きくはないのが通常だと思うので、まあ、まだ、RL78用のコンパイラは gcc やその他製品も含めて、改善すべき余地は多々あるのではないかと思います(なんかおれエラソーだな)。
※個人の印象です
私の認識では藤田さんは「中の人」以上ですよ。MFT2013で見た「IMSAI8080」をプレゼントしたいです!!
IMSAI いいですね! 我が家の住空間的にありえませんが広いとこに引っ越せたら手に入れたい逸品ですね。
実行時間の成績も更に(40数%くらいまでいけそうな感じ※)向上すると思われます。
と無責任に言ってたのが確認できたので投稿。
下記のコードの置き換えにより、167,533(ミリ秒) → 98,102(ミリ秒) で 41.4%向上。
インライン展開をもうちょっと頑張ればさらに実行時間は減らせるけれど、労力の割に意味がないのでこのへんで終了。
unsigned mod_pow16 (unsigned k, unsigned n); __asm ( "rb0bc = 0xffefa \n" "rb0de = 0xffefc \n" "rb1ax = 0xffef0 \n" "rb1bc = 0xffef2 \n" "rb1de = 0xffef4 \n" "rb1hl = 0xffef6 \n" " .section .text \n" " .global _mod_pow16 \n" " .type _mod_pow16, @function \n" "_mod_pow16: \n" " movw ax, [sp+6] \n" " cmpw ax, #1 \n" " bz $_mod_pow16_return0 \n" " movw bc, ax \n" " movw ax, [sp+4] \n" " movw rb1bc, ax \n" " movw ax, #16 \n" "_mod_pow16_loop0: \n" " subw ax, bc \n" " bnc $_mod_pow16_loop0 \n" " addw ax, bc \n" " movw rb1hl, ax \n" " movw rb1de, #1 \n" "_mod_pow16_loop1: \n" " movw ax, rb1bc \n" " shrw ax, 1 \n" " movw rb1bc, ax \n" " bnc $_mod_pow16_skip0 \n" " movw ax, rb1de \n" " movw bc, ax \n" " movw ax, [sp+6] \n" " movw de, ax \n" " movw ax, rb1hl \n" " movw rb1ax, #0 \n" " br $1f \n" "0: \n" " xchw ax, bc \n" " addw ax, ax \n" " subw ax, de \n" " sknc \n" " addw ax, de \n" " xchw ax, bc \n" "1: \n" " shrw ax, 1 \n" " bnc $2f \n" " sel rb1 \n" " addw ax, rb0bc \n" " subw ax, rb0de \n" " sknc \n" " addw ax, rb0de \n" " sel rb0 \n" "2: \n" " cmpw ax, #0 \n" " bnz $0b \n" " movw ax, r8 \n" " movw rb1de, ax \n" "_mod_pow16_skip0: \n" " movw ax, rb1bc \n" " or a, x \n" " bz $_mod_pow16_return \n" " movw ax, rb1hl \n" " movw bc, ax \n" " movw ax, [sp+6] \n" " movw de, ax \n" " movw ax, rb1hl \n" " movw rb1ax, #0 \n" " br $1f \n" "0: \n" " xchw ax, bc \n" " addw ax, ax \n" " subw ax, de \n" " sknc \n" " addw ax, de \n" " xchw ax, bc \n" "1: \n" " shrw ax, 1 \n" " bnc $2f \n" " sel rb1 \n" " addw ax, rb0bc \n" " subw ax, rb0de \n" " sknc \n" " addw ax, rb0de \n" " sel rb0 \n" "2: \n" " cmpw ax, #0 \n" " bnz $0b \n" " movw ax, r8 \n" " movw rb1hl, ax \n" " br $_mod_pow16_loop1 \n" "_mod_pow16_return0: \n" " movw r8, #0 \n" "_mod_pow16_return: \n" " ret \n" ); int32_t div_a (unsigned a, unsigned b); __asm ( " .section .text \n" " .global _div_a \n" " .type _div_a, @function \n" "_div_a: \n" " clrw ax \n" " movw r8, ax \n" " movw r10, ax \n" " movw ax, [sp+6] \n" " movw de, ax \n" " movw ax, [sp+4] \n" " mov c, #24 \n" "0: \n" " addw ax, ax \n" " cmpw ax, de \n" " skc \n" " subw ax, de \n" " not1 cy \n" " sel rb1 \n" " rolwc ax, 1 \n" " rolwc bc, 1 \n" " sel rb0 \n" " dec c \n" " bnz $0b \n" " ret \n" ); int32_t sigma_a (unsigned n, uint8_t j); __asm ( " .section .text \n" " .global _sigma_a \n" " .type _sigma_a, @function \n" "_sigma_a: \n" " subw sp, #12 \n" " movw ax, [sp+16] \n" " decw ax \n" " shlw ax, 3 \n" " movw bc, ax \n" " mov a, [sp+18] \n" " shrw ax, 8 \n" " addw ax, bc \n" " movw [sp+2], ax \n" " clrw ax \n" " movw [sp+8], ax \n" " movw [sp+6], ax \n" " movw [sp+4], ax \n" " br $1f \n" "0: \n" " movw ax, [sp+8] \n" " movw [sp], ax \n" " call !!_mod_pow16 \n" " movw ax, r8 \n" " movw [sp], ax \n" " call !!_div_a \n" " movw ax, [sp+6] \n" " addw ax, r8 \n" " movw [sp+6], ax \n" " movw ax, [sp+4] \n" " sknc \n" " incw ax \n" " addw ax, r10 \n" " movw [sp+4], ax \n" " movw ax, [sp+2] \n" " subw ax, #8 \n" " movw [sp+2], ax \n" "1: \n" " movw ax, [sp+8] \n" " movw bc, ax \n" " incw ax \n" " movw [sp+8], ax \n" " movw ax, [sp+16] \n" " cmpw ax, bc \n" " bnz $0b \n" " movw ax, [sp+6] \n" " movw r8, ax \n" " movw ax, [sp+4] \n" " movw r10, ax \n" " addw sp, #12 \n" " ret \n" ); int32_t div_b (unsigned a, unsigned b); __asm ( " .section .text \n" " .global _div_b \n" " .type _div_b, @function \n" "_div_b: \n" " movw ax, [sp+4] \n" " movw bc, ax \n" " movw ax, [sp+6] \n" " movw de, ax \n" " clrw ax \n" " movw r8, ax \n" " movw r10, ax \n" " incw ax \n" "0: \n" " addw ax, ax \n" " cmpw ax, de \n" " skc \n" " subw ax, de \n" " not1 cy \n" " sel rb1 \n" " rolwc ax, 1 \n" " rolwc bc, 1 \n" " sel rb0 \n" " ; xchw ax, bc \n" " ; subw ax, #1 \n" " ; xchw ax, bc \n" " dec c \n" " bnz $0b \n" " ret \n" ); int32_t sigma_b (unsigned n, uint8_t j); __asm ( " .section .text \n" " .global _sigma_b \n" " .type _sigma_b, @function \n" "_sigma_b: \n" " subw sp, #8 \n" " movw ax, [sp+12] \n" " shlw ax, 3 \n" " movw bc, ax \n" " mov a, [sp+14] \n" " shrw ax, 8 \n" " addw ax, bc \n" " movw [sp+2], ax \n" " movw ax, #24 \n" " movw [sp], ax \n" " clrw ax \n" " movw [sp+4], ax \n" " movw [sp+6], ax \n" "0: \n" " call !!_div_b \n" " movw ax, [sp+4] \n" " addw ax, r8 \n" " movw [sp+4], ax \n" " movw ax, [sp+6] \n" " sknc \n" " incw ax \n" " addw ax, r10 \n" " movw [sp+6], ax \n" " movw ax, [sp+2] \n" " addw ax, #8 \n" " movw [sp+2], ax \n" " movw ax, [sp] \n" " subw ax, #4 \n" " movw [sp], ax \n" " bnz $0b \n" " movw ax, [sp+4] \n" " movw r8, ax \n" " movw ax, [sp+6] \n" " movw r10, ax \n" " addw sp, #8 \n" " ret \n" );
労力の割に意味がないのでこのへんで終了。
と言った舌の根も乾かない内に更新。
ループ展開なんて卑劣なことをやった結果 167,533(ミリ秒) → 88,734(ミリ秒) で 47.0%向上。
もはや“理想的なコンパイラの出力コードの例”とかそういうのではないなあ。何やってんだか。
unsigned mod_pow16 (unsigned k, unsigned n); __asm ( " .section .text \n" " .global _mod_pow16 \n" " .type _mod_pow16, @function \n" "_mod_pow16: \n" " movw ax, [sp+6] \n" " cmpw ax, #1 \n" " bz $_mod_pow16_return0 \n" " movw bc, ax \n" " movw ax, [sp+4] \n" " movw r10, ax \n" " movw ax, #16 \n" "_mod_pow16_loop0: \n" " subw ax, bc \n" " bnc $_mod_pow16_loop0 \n" " addw ax, bc \n" " movw r12, ax \n" " movw r8, #1 \n" "_mod_pow16_loop1: \n" " movw ax, r10 \n" " shrw ax, 1 \n" " movw r10, ax \n" " bnc $_mod_pow16_skip0 \n" " movw ax, r8 \n" " movw bc, ax \n" " movw ax, [sp+6] \n" " movw de, ax \n" " movw ax, r12 \n" " movw hl, #0 \n" " br $1f \n" "0: \n" " xchw ax, bc \n" " addw ax, ax \n" " subw ax, de \n" " sknc \n" " addw ax, de \n" " xchw ax, bc \n" "1: \n" " shrw ax, 1 \n" " bnc $2f \n" " xchw ax, hl \n" " addw ax, bc \n" " subw ax, de \n" " sknc \n" " addw ax, de \n" " xchw ax, hl \n" "2: \n" " cmpw ax, #0 \n" " bnz $0b \n" " movw ax, hl \n" " movw r8, ax \n" "_mod_pow16_skip0: \n" " movw ax, r10 \n" " or a, x \n" " bz $_mod_pow16_return \n" " movw bc, r12 \n" " movw ax, [sp+6] \n" " movw de, ax \n" " movw ax, r12 \n" " movw hl, #0 \n" " br $1f \n" "0: \n" " xchw ax, bc \n" " addw ax, ax \n" " subw ax, de \n" " sknc \n" " addw ax, de \n" " xchw ax, bc \n" "1: \n" " shrw ax, 1 \n" " bnc $2f \n" " xchw ax, hl \n" " addw ax, bc \n" " subw ax, de \n" " sknc \n" " addw ax, de \n" " xchw ax, hl \n" "2: \n" " cmpw ax, #0 \n" " bnz $0b \n" " movw ax, hl \n" " movw r12, ax \n" " br $_mod_pow16_loop1 \n" "_mod_pow16_return0: \n" " movw r8, #0 \n" "_mod_pow16_return: \n" " ret \n" ); int32_t div_a (unsigned a, unsigned b); __asm ( " .section .text \n" " .global _div_a \n" " .type _div_a, @function \n" "_div_a: \n" " movw ax, [sp+6] \n" " movw de, ax \n" " movw ax, [sp+4] \n" " .rept 8 \n" " addw ax, ax \n" " cmpw ax, de \n" " skc \n" " subw ax, de \n" " rolwc bc, 1 \n" " .endr \n" " push bc \n" " .rept 16 \n" " addw ax, ax \n" " cmpw ax, de \n" " skc \n" " subw ax, de \n" " rolwc bc, 1 \n" " .endr \n" " movw ax, #0xffff \n" " subw ax, bc \n" " movw r8, ax \n" " pop bc \n" " mov a, #0xff \n" " sub a, c \n" " shrw ax, 8 \n" " movw r10, ax \n" " ret \n" ); int32_t sigma_b (unsigned n, uint8_t j); __asm ( " .section .text \n" " .global _sigma_b \n" " .type _sigma_b, @function \n" "_sigma_b: \n" " movw ax, [sp+4] \n" " shlw ax, 3 \n" " movw bc, ax \n" " mov a, [sp+6] \n" " shrw ax, 8 \n" " addw ax, bc \n" " movw r12, ax \n" " mov r14, #6 \n" " clrw ax \n" " movw hl, ax \n" " movw de, ax \n" "0: \n" " mov a, r14 \n" " mov c, a \n" " clrw ax \n" " movw r8, ax \n" " movw r10, ax \n" " incw ax \n" "1: \n" " .rept 4 \n" " addw ax, ax \n" " cmpw ax, r12 \n" " skc \n" " subw ax, r12 \n" " not1 cy \n" " sel rb1 \n" " rolwc ax, 1 \n" " rolwc bc, 1 \n" " sel rb0 \n" " .endr \n" " dec c \n" " bnz $1b \n" " movw ax, hl \n" " addw ax, r8 \n" " movw hl, ax \n" " movw ax, de \n" " sknc \n" " incw ax \n" " addw ax, r10 \n" " movw de, ax \n" " movw ax, r12 \n" " addw ax, #8 \n" " movw r12, ax \n" " dec r14 \n" " bnz $0b \n" " movw ax, hl \n" " movw r8, ax \n" " movw ax, de \n" " movw r10, ax \n" " ret \n" );
IAR C/C++ Compiler for Renesas RL78 1.30.5 に、最適化の項目として「ショートアドレス作業エリア」というのを見つけたので、試してみることとした。
2014/04/20 一部数値修正
2014/04/20 IAR C/C++ Compiler for Renesas RL78 1.40.1 の情報を追加
2014/04/20 CA78K0R v1.70 も試したが結果に違いはなかったので略
2014/05/15 GNURL78 v14.01 Windows Tool Chain (ELF) の情報を追加
なんだかすっかりIAR慣れされてますね^^。高(バランス)+ショートアドレス作業エリア有効(20バイト)で一気に実行時間が遅くなってますね。それぞれの最適化の使い分けは確かに難しそうです。
関数 millis() が、呼び出すタイミングによっては不正確な値を返す不具合があったので修正。
#if defined(__GNUC__) #define di() __asm __volatile("DI;") #elif defined(__CA78K0R__) #pragma di #define di() DI() #elif defined(__ICCRL78__) #define di() __disable_interrupt() #endif uint32_t millis() { uint32_t m; di(); m = millisCount; ei(); return m; }
尚、不具合が起こりうるのは 65,536ミリ秒の倍数近辺のタイミングであり、一連の成績には影響はなかった模様。
「処理性能を3倍高速化」 で話題の RL78用のコンパイラ CC-RL がリリースされた ので試してみた。
CA78K0R から CC-RL 対応でプログラム書き換えた部分のみ抜粋
#elif defined(__CA78K0R__) #pragma sfr #pragma ei #define ei() EI() #pragma di #define di() DI() #define _SAU0EN SAU0EN #define _SMR02 SMR02 #define _SCR02 SCR02 #define _SPS0 SPS0 #define _SDR02 SDR02 #define _SOE02 SOE0L.2 #define _SS0 SS0 #define _P0 P0 #define _PM0 PM0 #define _SSR02L SSR02L #define _TXD1 TXD1 #define _TAU0EN TAU0EN #define _TPS0 TPS0 #define _TMR00 TMR00 #define _TDR00 TDR00 #define _TS0Lbit0 TS0L.0 #define _TMIF00 TMIF00 #define _TMMK00 TMMK00 #define _TCR00 TCR00 #pragma interrupt INTTM00 inttm00 #define lrintf(f) ((int)(f)) #elif defined(__CCRL__) #define ei() __EI() #define di() __DI() #include "iodefine.h" #define _SAU0EN SAU0EN #define _SMR02 SMR02 #define _SCR02 SCR02 #define _SPS0 SPS0 #define _SDR02 SDR02 #define _SOE02 SOE0L_bit.no2 #define _SS0 SS0 #define _P0 P0 #define _PM0 PM0 #define _SSR02L SSR02L #define _TXD1 TXD1 #define _TAU0EN TAU0EN #define _TPS0 TPS0 #define _TMR00 TMR00 #define _TDR00 TDR00 #define _TS0Lbit0 TS0L_bit.no0 #define _TMIF00 TMIF00 #define _TMMK00 TMMK00 #define _TCR00 TCR00 #pragma interrupt inttm00(vect=INTTM00) #define lrintf(f) ((int)(f)) #elif defined(__ICCRL78__)
確かに性能いいですね。C++対応してくれれば喜んでArduinoライブラリもポーティングするんですけどね。
M16Cシリーズ,R8Cファミリ用C/C++コンパイラ製品であるNC30 からのプログラム移行支援機能なんてのが用意されてる割には片手落ちな気がしますね >C++非対応
おお!もうCC-RLでテストされている!
C++に対応してくれれば・・・KURUMIも2倍は早くなるのに・・・
GCC の float が遅い(そしてコードサイズがでかい)のは浮動小数点演算ルーチンが C で汎用的に書かれてることが原因として大きいと思うので、そこら辺ルネサスさんから CA78K0R や CC-RL 用に書かれた(恐らくはアセンブラで書かれた)カリカリのコードを GPL で公開されて gcc にコミットされれば性能は結構向上すると思うのですけどねー、開発ツールの販売も商売の一部分としてされてるルネサスさんでは社内的には難しいのでしょうか。
他社の話になりますが、Atmel の AVR 用にポートされた gcc ではライブラリ avr-libc というのがアセンブラで書かれていてコードサイズが小さく実行速度の性能的にも優れているのですが、そういうのが RL78 にもあると嬉しいですね。まあ Atmel では純正の開発ツールが gcc なので事情も違うのでしょうが。