積極的にメモっていく姿勢

題名詐欺。更新頻度の低さが売り。

マイコンカーラリー講習会に関連してやってみたM16Cメモリ節約

08/08(火) 開催の北海道工業科教員向けのマイコンカー講習会参加にあたり,
画像処理のマイコンカーを紋別高校の先生から借りてコードを眺めていた.
実際に色々書き換えて試すことができればよかったのだが,祖父の不幸とも重なり,
十分に実験はできずに返却することに.(自分で作ればいつでもできるぞ)
 
講習会では,共通する部分のコードについて書き換えてみた.
PIC XC8 の時とは違う結果が出たので,残しておくことにした.
(PICでサイモンを作ったときに,メモリ節約のためにできることを試している.)

環境

f:id:tomio2480:20170808184147p:plain
劇的に古いが,気づいたのは全部終わった後でしたので,ご容赦いただけると……
 
先に書いておきますが,今回変化があったのは,PROGRAM SECTIONのみだった.
RAMDATA SECTION と ROMDATA SECTION には変化がなかったため割愛.
 
また,処理速度に関しての評価もしていない,超ガバガバ調査なのであしからず.

2で割る

まずは,除算部分をビットシフトに書き換えた.

// 元のコード
servoPwmOut( iServoPwm / 2 );

// 書き換えたコード
servoPwmOut( iServoPwm >> 1 );

[元] PROGRAM SECTION: 00006204 Byte(s)
[改] PROGRAM SECTION: 000061fe Byte(s)
 
0x00006204 - 0x000061fe = - 6 Byte
 
分量はともかくとして節約成功.

剰余

2のn乗で割った時の余りを求める場合,
value % 2n としないで value & (2n - 1) としても同じ結果になる.

// 元のコード
i = (cnt1/200) % 2 + 1;

// 書き換えたコード
i = (cnt1/200) & 1 + 1;

[元] PROGRAM SECTION: 00006254 Byte(s)
[改] PROGRAM SECTION: 00006250 Byte(s)

0x00006254 - 0x00006250 = - 4 Byte
 
value % 2 の場合は上記の通り.value % 4 も試した.

// 元のコード
led_out( 1 << (cnt1/50) % 4 );

// 書き換えたコード
led_out( 1 << (cnt1/50) & 3 );

[元] PROGRAM SECTION: 000063d0 Byte(s)
[改] PROGRAM SECTION: 000063ce Byte(s)

0x000063d0 - 0x000063ce = - 2 Byte
 
使用している式が違うので,一概にどうとは言えないけれど,
数値あるいは使用する状況によって節約できる分は変わるみたい.

インクリメントの前置と後置

後置インクリメントは更新前の値を用いるため,
その値が必要なければ前置インクリメントの方が,処理も少なく速度も出るぞ.
という話を聞いたことがあったので,メモリには影響ないのか確かめた.

// 元のコード
cnt_saka++;

// 書き換えたコード
++cnt_saka;

[元] PROGRAM SECTION: 000063d4 Byte(s)
[改] PROGRAM SECTION: 000063d4 Byte(s)

0x000063d4 - 0x000063d4 = 0 Byte
 
どの領域においても変化はなかった.
速度面でのメリットがなければ,どっちでもいい感じがする.
追加で以下のようなコードがあったので,試しに変えてみた.

// 元のコード
iTimer10++;
switch( iTimer10 ) {

// 書き換えたコード
switch( ++iTimer10 ) {

[元] PROGRAM SECTION: 000063ce Byte(s)
[改] PROGRAM SECTION: 000063ce Byte(s)

0x000063ce - 0x000063ce = 0 Byte
 
これも変化はなかった.

関数内の変数を削る

現段階で値を使用する様子は見られなかったので,削ってみる.

// 元のコード
    unsigned char b;
    int ret = 0;

    if( ( center_inp() == 1 ) && ( sensor_inp() == 0x0c ) ) {
        ret = 1;
    }
    return ret;

// 書き換えたコード
    if( ( center_inp() == 1 ) && ( sensor_inp() == 0x0c ) ) {
        return 1;
    }
    return 0;

[元] PROGRAM SECTION: 00006650 Byte(s)
[改] PROGRAM SECTION: 00006648 Byte(s)

0x00006650 - 0x00006648 = - 2 Byte
 
何かに使用する予定だったのか,どうかはわからない変数があったので,
純粋に return するためだけの変数を削った効果かどうかがわからない感じ.
 
ちなみに,b という変数はセンサから読み取った値を一時的に保管し,
判断する際に数回読みに行くことで生じる状態の差を潰したかったのではないか,
と思っている.(が,2回だから大丈夫という判断なのかも?)
 
現実,もっと細かい多くの条件で分岐する箇所は,
センサの状態を変数に保管してから判断を行なっている.
何回読み取るにせよ,保管してから比較した方がより正しい判断ができる気がする.
メモリ節約した結果,誤動作引き起こしましたー!ではダメなので……
 
もう一つ,書き換えてみた関数の中身も書いておく.
 

// 元のコード
    unsigned char data;
    data = p7 >> 4;
    return data;

// 書き換えたコード
    return p7 >> 4;

[元] PROGRAM SECTION: 00006648 Byte(s)
[改] PROGRAM SECTION: 00006640 Byte(s)

0x00006648 - 0x00006640 = - 8 Byte

劇的な節約効果があった.
実際,この関数に期待している処理がこれだけで済むのであれば,
関数化しなくてもいいのかもしれないと思ったりもした.
(可読性の観点から言えば必要だと思うけど,実際動かす段階なら……)

0 にする

前回 XC8 で試したときに効果があったので試した.

// 元のコード
cnt1 = 0;

// 書き換えたコード
cnt1 ^= cnt1;

[元] PROGRAM SECTION: 000062aa Byte(s)
[改] PROGRAM SECTION: 000062ba Byte(s)

0x000062aa - 0x000062ba = + 16 Byte

XC8の時は減ったけど,今回は増えた.
全然深追いしていないけど,増えるのでこれは却下.

色々試したけれど

まずは動くものを作ることを忘れてはいけない.
同じ結果をもたらす複数の表現を自分の中で持っておくと,
より軽量,高速な成果をもたらすことができることは間違いない.
 
金をつめば…… マイコンを変えれば…… コンパイラを変えれば……
色々実験する余地はあるけれど,レギュレーション内でできることが何か,
という観点からコードを見直すことも大切だ.