RX65N Envision Kit でレイ・トレーサーを走らせてみました。

M5Stack や、ESP32 では、定番のようなので、ソースを取得、元々 Arduino 用なので、改造して動かしてみました。

ESP32(160MHz) では13秒らしいですが、120MHzのRX65では7.7秒なので、かなり高速ですね。

※速度比較の為320×240でレンダリングしてます。

ソースコードは以下に・・

https://github.com/hirakuni45/RX/tree/master/RTK5_RAYTRACER

 

  • >10倍ちょっとの高速化が果たせました
    凄いですね!
    凄いです。
    CPUを知り尽くし、コンパイラを知り尽くしてるfujitaさんだからこそと思います。

    >今回はこの辺りで止めておきますが
    いやもう十分です。
    素晴らしい成果です。
  • > ESP32(160MHz) では13秒らしいですが、120MHzのRX65では7.7秒なので、かなり高速ですね。

    レイトレーサー本体の関数 doRaytrace() の第1引数は疑似的なアンチエリアシングを行うための値であり、これに 2 以上の値を指定すると画質が向上する反面計算が繰り返されるのでその値分計算時間が倍増しますが

    github.com/.../RTK5_RAYTRACER
    ↑ doRaytrace() の第1引数の値は 4

    macsbug.wordpress.com/.../
    ↑ でリンクされてる TFT22_raytrace.zip 中の TFT22_raytrace.ino の中での doRaytrace() の第1引数の値は 1

    github.com/.../main.c
    ↑ での doRaytrace() の第1引数の値は 1

    になっており、おおよそ比較にはなってない感じです。

  • doRaytrace() の第1引数に 1 を指定してレンダリングした結果:

    4 を指定してレンダリングした結果:

    おわかりいただけただろうか

  • 確かに、「raysPerPixel」は「1」でベンチマークを行っているようです。

    RX65Nでも、FSQRTマクロなどを盛り込み、同条件で再度ベンチマークを行いました。

    上記のように、0.83秒でレンダリングします。
    fujita nozomu」さんありがとうございます。

  • 嬉しいご報告をありがとうございます。

    あともう一方の他社製品ですが

    • デザインが比較的近年の 32bit プロセッサ
    • 浮動小数点演算機能搭載
    • クロック 百数十MHz

    RX65N と比べてスペック的には似たようなものだと思うので、それほどの大きな差が出ることは考えづらくおそらくは何かパフォーマンスが出ていない原因があるのではないかと思っています。機会があればその辺りを検証するのも興味深い気がしますね。

  • 自己レス

    > 更なる高速化を目指すには raytracer.hpp の中身に手を入れる必要が出てくると思います。

    raytracer.hpp の中で単位ベクトルの計算をしている箇所があり、

      vec3 operator!()              const { return *this*(1.0f/sqrtf(*this%*this));  }  // Normalized vector
    

    1.0f/sqrtf() で平方根の逆数を求めていますが、高速に平方根の逆数を求めるアルゴリズムが知られておりこれを適用することで高速化ができそうです。1.0f/sqrtf() を RX64M 用に効率的なコードに翻訳したとすると平方根の計算に 16サイクル、直値 1.0f のロードに 1サイクル、除算に 16サイクルの合計 33サイクルを要することとなりますが、先にリンクした記事のコードを g++ でコンパイルすると

    $ rx-elf-g++ -O3 -mcpu=rx64m -c Q_rsqrt.cpp ; rx-elf-objdump -d Q_rsqrt.o
    
    Q_rsqrt.o:     file format elf32-rx-le
    
    
    Disassembly of section P:
    
    00000000 <__Z7Q_rsqrtf>:
       0:   fd 81 15                        shlr    #1, r1, r5
       3:   fb 42 df 59 37 5f               mov.l   #0x5f3759df, r4
       9:   ff 05 54                        sub     r5, r4, r5
       c:   fd 72 31 00 00 00 3f            fmul    #0x3f000000, r1
      13:   fc 8f 51                        fmul    r5, r1
      16:   fc 8f 51                        fmul    r5, r1
      19:   fb 42 00 00 c0 3f               mov.l   #0x3fc00000, r4
      1f:   ff 81 14                        fsub    r1, r4, r1
      22:   fc 8f 51                        fmul    r5, r1
      25:   02                              rts
    
    $
    

    インライン展開することを前提として rts を除き合計 14サイクルで平方根の逆数が計算できることとなります。

    raytracer.hpp の該当箇所より前の方に先にリンクした記事のコードをほゞそのまゝ

    static inline float Q_rsqrt( float number ) __attribute__((always_inline));
    static inline float Q_rsqrt( float number )
    {
    	union {
    		float f;
    		uint32_t i;
    	} conv;
    	
    	float x2;
    	const float threehalfs = 1.5F;
    
    	x2 = number * 0.5F;
    	conv.f  = number;
    	conv.i  = 0x5f3759df - ( conv.i >> 1 );
    	conv.f  = conv.f * ( threehalfs - ( x2 * conv.f * conv.f ) );
    	return conv.f;
    }
    

    コピペして追加し、該当箇所を

      vec3 operator!()              const { return *this*(Q_rsqrt(*this%*this));  }  // Normalized vector
    

    と変更するだけで対応できます。

    これで計算時間のみを計測すると

    → 1040(m秒)

    変更前の 1.385m秒に対しておよそ 25% の計算時間の短縮ができました。

    出力を変更前のものと比較すると演算の誤差による違いもあるのですが

    表示をしても気になるものではないようです。

  • > 出力を変更前のものと比較すると演算の誤差による違いもあるのですが表示をしても気になるものではないようです。

    …………そんなふうに考えていた時期が俺にもありました

    (↓の画像をクリックしてみてね)

    重ねてみたら誤差ありすぎワロタwww

    Q_rsqrt() の conv.f = conv.f * ( threehalfs - ( x2 * conv.f * conv.f ) ); は 2回反復して誤差を減らすのが良いようです。

    static inline float Q_rsqrt( float number ) __attribute__((always_inline));
    static inline float Q_rsqrt( float number )
    {
    	union {
    		float f;
    		uint32_t i;
    	} conv;
    	
    	float x2;
    	const float threehalfs = 1.5F;
    
    	x2 = number * 0.5F;
    	conv.f  = number;
    	conv.i  = 0x5f3759df - ( conv.i >> 1 );
    	conv.f  = conv.f * ( threehalfs - ( x2 * conv.f * conv.f ) );
    	conv.f  = conv.f * ( threehalfs - ( x2 * conv.f * conv.f ) );
    	return conv.f;
    }
    

    これで重ねてもさほど気にならない程度まで演算精度を高めることができるようです。

    但し計算時間が

    →1102(m秒)

    に落ちてしまいます(これは悔しい)。