SMART Response XEを使う!その3

f:id:PocketGriffon:20211118081632j:plain

SMART Response XEでプログラムを組める環境が整い、いくつかのコードを書いてみた。

ある程度作れるようになってくると、今度は「どうやったら速いコードが組めるのか」「何をしたら遅くなるのか」が気になってくる。

レトロプログラマたるもの、常にコードサイズと速度は気にしておきたい(^^)

 

SMART Response XEのCPUは?

Arduino IDEを利用することもあり、てっきりCPUはARM系の何かかと思いこんでいたら、どうやらAVRというらしい。恥ずかしながらAVRというプロセッサを知らなかった。

おそらくCPUと周辺機器を含んだチップになってるんだろうと勝手に想像。

 

どんなCPUなんだろうな〜…と思い、データシートを調べてみたら……

f:id:PocketGriffon:20211118084635j:plain

なんとレジスタ長が8bitのRISC CPUだった!!

30年近く前に同じく8bitレジスタRISC CPUを扱った事があったけれど、またしてもこんな楽しそうなCPUに巡り会えるとは!!(^-^)

 

これを見る限りXYZは16bitのレジスタペアとして使えそうだけど、実際にはこのレジスタだけでなくペアで使う事が出来るっぽい。XYZレジスタを用いることで、一部の命令が便利に使えるみたい?

RISCプロセッサらしく割り切ったアドレッシングのみとなっている。

XEはこのCPUが16MHzで動いているらしい。

 

アセンブラコードを見てみたい!

CPUの素性が分かってくると、今度はどんなコードが出力されているのかを見てみたくなる衝動に駆られる!そりゃそうでしょう!(^-^)

 

Arduino IDEから呼び出されているコンパイラはavr-gccという。

f:id:PocketGriffon:20211118091338j:plain

avr-gccコマンドの置かれている場所(パス)は、Arduino IDEでビルドした後にウィンドウ下側(赤で囲った部分)に出力されているログを解析すれば良い(^^)

 

パス/avr-gcc -S p.c

これでp.cのアセンブラリストを見ることが出来る。

 

unsigned char func(unsigned char a, unsigned char b, unsigned char c)
{
  return a + b + c;
}

このソースリストが……

f:id:PocketGriffon:20211118092457j:plain

こんな風に変換される。

おそらくR24:a、R22:b、R20:cという感じで引数が渡り、戻り値がR24に入るのだろう。

 

この例では8bit長の値を渡しているが、これをint長(sizeof(int) == 2)で渡してみる。

 

int func(int a, int b, int c)
{
  return a + b + c;
}

f:id:PocketGriffon:20211118093110j:plain

わかりやすくコード部分のみ抜き出してみたが、8bit長ならば2命令で済んでいたところ、int長にしたら倍の4命令となった。

 

avr-gccのint型は16bitサイズであり、8bitレジスタのAVRではこのような結果となる。

同様な事はZ80でSDCCやLSIC80といったコンパイラを使った時にも起こり、int=4/8のCPUと比べて、より一層の意識が必要となる……気がする(^^)

 

ちなみにアセンブラリストを見るのはavr-objdumpでも可能だ。

しかし最適化が強烈に掛かるせいかソースと混在表示させると非常に分かりづらい。AVRアセンブラに慣れている方ならまだしも、私のような初心者では逆に解析がしづらい気がする。

 

処理時間の測定

書いたコードが速くなったのか遅くなったのかを知りたかったので、処理時間を測定する方法を調べてみた。

……と思ったら、すでにM5Stackなどで試した方法と同じだった!(^^;;

micros()を呼び出すと、その時の経過時間を返してくれるので、それを利用する。

戻り値の単位はマイクロ秒で、1000でミリ秒、1000*1000で1秒だ。

 

例の1バイト4ドット構成のフレームバッファから、データ変換しつつLCDCへ転送する関数に掛かる時間を図ってみたら55916マイクロ秒だった。つまり55.9ミリ秒。

ということは1秒間で17.9回の画面更新が出来るって事ね。

もう少し速くしておきたいけれども、これはあとの楽しみ…(^-^)

 

C言語で気をつける点など

C言語で記述する際、気をつけている点などを書いてみようと思う。

あくまでもAVR-CPU限定の話だ。

実際には最適化は最後の最後で良いと思うし、動けば正義!とも言えるので細かい点は気にしなくても良いとは思う。究極の最適化は趣味レベルでやりたいけど!(^^)

 

intが16ビット

↑にも書いているが、sizeof(int)は2であり、16ビット長となっている。

AVRのマシン語的には8ビットサイズが一番扱いやすいので、もしも数値が8ビット長に収まるのであれば積極的に使っていきたい。

派手にやりすぎると融通効かないコードになるし読みづらくなる!

あくまでもここぞ!ってコードに適用していきたい。

 

mul命令が使われない?

マシン語レベルでは存在する乗算命令(mul)なんだけど、8ビット同士の乗算(a * b)をしても乗算命令が出力されない。乗算関数を呼び出すコードが出てしまう。呼び出された先ではmul命令が使われているんだと思うけど、CALLする分もったいなく感じる…。

 

ループの奥の方では乗算命令を使わないアルゴリズムを検討した方が良さそうだ。

もっとも、私のコードの書き方が悪いのかも知れないが…汗

 

除算命令がない

AVR自体、マシン語レベルでの除算命令が存在しない。

以前、Raspberry Pi Picoでも同じ感じでハマったけれども、最近は意識してない事が多かったので油断大敵!(^^;;

 

シフト回数は1回ずつ

最近では複数回を一気にシフトしてくれる(バレルシフタを積んだ)CPUが多い印象があるけれども、AVRのシフト命令は1回ずつシフトする。

例えば3回シフトするプログラムを書いた場合には、きっちりと3回のシフト命令が出力される。4回になるとswap(上位下位の4ビットを入れ替える命令)が出るので最適化はされてるみたいだ!

ARMでプログラムを組み慣れていると、なんとなくシフトはタダみたいなもんのイメージで使ってしまう(^^;; 気をつけなくちゃ!

 

さて……

ここまで作れるようになってくると、なにかまとまったものを作ってみたい気もするけれども……いつものパターンだとここで……飽きる(^^;;

でも頑張って何かひとつ作ってみましょうかね…(遠い目

 

ではまた次回!(^-^)ノ