M5Stack や、ESP32 では、定番のようなので、ソースを取得、元々 Arduino 用なので、改造して動かしてみました。
ESP32(160MHz) では13秒らしいですが、120MHzのRX65では7.7秒なので、かなり高速ですね。
※速度比較の為320×240でレンダリングしてます。
ソースコードは以下に・・
https://github.com/hirakuni45/RX/tree/master/RTK5_RAYTRACER
基本 float で計算するプログラムの様ですが、ceil() や sqrt() の double の関数、0.01 や 1.0 、10.9 の double の値が使用されており、float と double で精度が異なる環境では実行パフォーマンスに影響がある気がします。 RX65N + CC-RX で double をソフトで計算する設定とかだと影響も小さくないのでは。
makefile を拝見したところ g++ を使用して RXv1 の命令を吐かせているようですが、折角の RX65N なのだから RXv2 命令に正しく対応してる筈(よく知らん)の CC-RX を使うか、あるいは g++ を工夫して使用して 3オペランド命令や FSQRT 命令が活用されればいくらかのパフォーマンス向上は期待できるのでは。
3オペランド命令が使えない RXv1 と 使える RXv2 の極端な比較
$ cat -n 3ope.cpp 1 float mul(float, float a, float b) 2 { 3 return a * b; 4 } 5 6 float add(float, float a, float b) 7 { 8 return a + b; 9 } 10 11 float sub(float, float a, float b) 12 { 13 return a - b; 14 } $ rx-elf-g++ -Wall -Wextra -O2 -c 3ope.cpp && rx-elf-objdump -d 3ope.o 3ope.o: file format elf32-rx-le Disassembly of section P: 00000000 <__Z3mulfff>: 0: ef 21 mov.l r2, r1 2: fc 8f 31 fmul r3, r1 5: 02 rts 00000006 <__Z3addfff>: 6: ef 21 mov.l r2, r1 8: fc 8b 31 fadd r3, r1 b: 02 rts 0000000c <__Z3subfff>: c: ef 21 mov.l r2, r1 e: fc 83 31 fsub r3, r1 11: 02 rts $ rx-elf-g++ -mcpu=rx64m -Wall -Wextra -O2 -c 3ope.cpp && rx-elf-objdump -d 3op e.o 3ope.o: file format elf32-rx-le Disassembly of section P: 00000000 <__Z3mulfff>: 0: ff b1 23 fmul r2, r3, r1 3: 02 rts 00000004 <__Z3addfff>: 4: ff a1 23 fadd r2, r3, r1 7: 02 rts 00000008 <__Z3subfff>: 8: ff 81 32 fsub r3, r2, r1 b: 02 rts $
FSQRT 命令が使えない RXv1 と 使える RXv2 の極端な比較
$ cat -n fsqrt.cpp 1 #include <cmath> 2 3 #if defined(__RXv2__) 4 #define sqrtf(_x) \ 5 ({ \ 6 float _y; \ 7 __asm __volatile( \ 8 "fsqrt %1, %0\n" \ 9 : "=r"(_y) \ 10 : "g"(float(_x)) \ 11 ); \ 12 _y; \ 13 }) 14 #endif 15 16 float hoge(const float a[3]) 17 { 18 return sqrtf(a[0]) + sqrtf(a[1]) + sqrtf(a[2]); 19 } $ rx-elf-g++ -Wall -Wextra -O2 -c fsqrt.cpp && rx-elf-objdump -d fsqrt.o fsqrt.o: file format elf32-rx-le Disassembly of section P: 00000000 <__Z4hogePKf>: 0: 6e 6a pushm r6-r10 2: ef 17 mov.l r1, r7 4: ec 11 mov.l [r1], r1 6: 05 00 00 00 bsr.a 6 <__Z4hogePKf+0x6> a: ef 1a mov.l r1, r10 c: a8 79 mov.l 4[r7], r1 e: 05 00 00 00 bsr.a e <__Z4hogePKf+0xe> 12: ef 16 mov.l r1, r6 14: a8 f1 mov.l 8[r7], r1 16: 05 00 00 00 bsr.a 16 <__Z4hogePKf+0x16> 1a: ef a7 mov.l r10, r7 1c: fc 8b 67 fadd r6, r7 1f: fc 8b 71 fadd r7, r1 22: 3f 6a 05 rtsd #20, r6-r10 $ rx-elf-g++ -Wall -Wextra -O2 -mcpu=rx64m -c fsqrt.cpp && rx-elf-objdump -d fs qrt.o fsqrt.o: file format elf32-rx-le Disassembly of section P: 00000000 <__Z4hogePKf>: 0: fc a0 13 fsqrt [r1].l, r3 3: fc a1 14 01 fsqrt 4[r1].l, r4 7: fc a1 15 02 fsqrt 8[r1].l, r5 b: ff a1 34 fadd r3, r4, r1 e: fc 8b 51 fadd r5, r1 11: 02 rts $
自己レス
> 基本 float で計算するプログラムの様ですが、ceil() や sqrt() の double の関数、0.01 や 1.0 、10.9 の double の値が使用されており、float と double で精度が異なる環境では実行パフォーマンスに影響がある気がします。
RX65N と同一の RXv2 コア採用の RX64M@96MHz を搭載している GR-KAEDE で動作させて検証してみました。液晶は適当なのが手持ちでなかったのと面倒だったので表示はしていません。計算のみです。
web コンパイラ ではコンパイルオプションを弄ったりできないので、web コンパイラで空のプロジェクトを作成してビルドを行いそれをダウンロードし、ローカルで makefile を弄ってローカルでビルド等しています。
レイトレーサーのプログラム本体 raytracer.hpp が C++11 でないとビルドできないようなので makefile のコンパイルオプションに -std=c++11 を追加、toolchain も https://gcc-renesas.com/ で配布している最新の GCC for Renesas 4.8.4.201801-GNURX Windows Toolchain (ELF) に変更して下記のスケッチをビルドして実行します。
/* GR-KAEDE Sketch Template V1.22 */ #define OutputPPM 1 // -std=c++11 を指定すると WCharacter.h でエラーになることへの対策 int isascii(int); int toascii(int); #include <Arduino.h> extern "C" void draw_pixel(int, int, int, int, int) __attribute__((noinline)); #include "raytracer.hpp" void setup() { const int w = 320, h = 240; Serial1.begin(230400); Serial1.println("start"); #if OutputPPM Serial1.println("P3"); Serial1.print(w); Serial1.print(" "); Serial1.println(h); Serial1.println("255"); #endif uint32_t start = millis(); doRaytrace(1, w, h); Serial1.println(millis() - start); } void loop() { } void draw_pixel(int x, int y, int r, int g, int b) { (void)x; (void)y; #if OutputPPM Serial1.println(r); Serial1.println(g); Serial1.println(b); #else (void)r; (void)g; (void)b; #endif }
Serial1 に PPM 形式 で画像イメージが出力されるので、GIMP2 で読み込んでみると
それらしい画像が表示されたので計算はできているようです。
スケッチ中の OutputPPM の値を 0 に変更し PPM の出力を止め計算時間だけを計測してみます。
→ 30425(m秒)
かなり遅いですね。
GR-KAEDE は web コンパイラでのコンパイルで RXv2 命令を有効化する -mcpu=rx64m が指定されているのは良いのですが最適化指示が設定されておらず効率の悪いコードを出力します。ちょっとこれではどうしようもないので makefile を弄って CFLAGS に -O3 を追加し、再度計算時間を計測してみます。
→ 15092(m秒)
先のと較べ半分以下の計算時間となりました。がまだ遅いですね。
GR-KAEDE は web コンパイラでのコンパイルでは double の演算を 64bit で行う設定となっており、GR-KAEDE に搭載されている RX64M は 64bit の浮動小数点演算を機能として持っておらずソフトウェアでの実装となっているため double の演算は float のそれに比べ大変重いものとなっています。今回のレイトレーサーのプログラムは float も double も 32bit である Arduino で動作させるものだった(?)為か一部 double の直値が使用されていたり sqrt() や ceil() の double の数学関数が使用されており、GR-KAEDE の仕様に引っかかりそうです。raytracer.hpp の中の double の直値を float に修正、同様に sqrt() → sqrtf()、ceil() → ceilf() の修正を行い再度ビルドして計算時間を計測してみます。
→ 3990(m秒)
かなり改善しました。が折角なのでもう少し攻めてみましょう。
RX64M の RXv2 コアは浮動小数点演算命令の中に平方根を計算する FSQRT 命令というのが追加されています。今回のレイトレーサーのプログラムは平方根の関数を複数個所で呼んでいるのでこれを FSQRT 命令に置き換えれば幾らかの計算時間の短縮が期待できそうです。
static inline float fsqrt(float x) __attribute__((always_inline)); static inline float fsqrt(float x) { __asm __volatile( "fsqrt %0, %0\n" \ : "+r"(x) \ ); return x; } #define sqrtf(x) fsqrt(x)
スケッチの raytracer.hpp をインクルードしている箇所の上に上記のコードを挿入すると単精度の平方根関数である sqrtf() を呼んでいる部分で FSQRT 命令が使用されます。これをビルドし実行すると
→ 1416(m秒)
大きな効果がありました。良い感じです。
他、前述の通り今回のレイトレーサーのプログラムは数学関数として sqrt() の他に「引数の値より大きい最小の整数の値」を返す ceil() が使用されており、これをなんとかすることでレイトレーサーの計算部分で外部関数の使用を完全になくすことができます。
static inline int roundUp(float x) __attribute__((always_inline)); static inline int roundUp(float x) { int y; __asm __volatile( "pushc fpsw\n" "mvtc #0b10, fpsw\n" "round %0, %0\n" "popc fpsw\n" : "=r"(y) \ : "r"(x) \ ); return y; } #define ceilf(x) roundUp(x)
スケッチの raytracer.hpp をインクルードしている箇所の上に上記のコードを挿入することで ceilf() を置き換えます。これをビルドし実行すると
→ 1385(m秒)
先の sqrtf() の置き換えと比べ効果は僅かな結果となりました。
今回はこの辺りで止めておきますが同じボードで更なる高速化を目指すには raytracer.hpp の中身に手を入れる必要が出てくると思います。
今回の実験で、同一の toolchain、同一のコンパイルオプションの条件で、僅かなコードの修正で 15092(m秒) → 1385(m秒) と 10倍ちょっとの高速化が果たせました。今回使用した GR-KAEDE 以外でも、マイコンの性能がどれだけ引き出せるかは
以上の 3点の理解が大きいことは共通してるのではないかと思います。異なる製品の性能比較を公平に行うには両製品への深い理解が要求され、易しい仕事ではないのかもしれません。
gr-kaede.raytracer.zip