mbed のバイナリサイズを減らす方法

この記事は mbed Advent Calendar 2015 - Adventar の2日目の記事です。

mbed のバイナリサイズを減らす方法

Flash メモリが多めに搭載されたデバイスを使う場合にはそれほど問題になりませんが、LPC1114FN28等の小さめのデバイスを使用するとユーザコードがROMに入らない場合があります。その場合には、以下の項目を試してみてください。

printf を使用しない

printf 系の書式指定が出来る標準関数は、一般的にリンクされるコードサイズが大きくなります。一カ所でも printf を使用すると、比較的大きな実行用のコードがリンクされるので、最終的なバイナリファイルがユーザコードに対して大きく見えることがあります。プログラム中で printf をデバッグ用途にだけ使用している場合は、プログラムの動作が確認できた後に積極的に削除しましょう。

インライン関数を使う(インライン展開されやすくなるように記述する)

同じソースファイルに含まれていない関数の呼び出しは、ローカル変数の待避や復帰、関数呼び出しのためのジャンプ命令などが生成され、コードサイズや実行時間も増加します。関数はなるべく呼び出される機能毎に一つのソースファイル中に纏めるなどすると、コンパイラが自動的にインライン展開を行ってコードサイズが小さくなる場合があります(mbedで使用しているオンラインコンパイラの最適化オプションは、自動インラインを行うモードに設定されています)。 ただし、インライン展開される関数自体ののサイズが大きく、呼び出される箇所が多い場合は逆に全体のコードサイズが増えるので、注意が必要です。

実行時アサートを無効にする

mbed ライブラリでは、実行時に指定ピンのチェックが行われ、不正なピンが使用されると標準出力にエラーを出力するようになっています。通常プログラムの動作が確認できた後は、このアサート機能は不要になります。エラーチェックを無効にすることでコードサイズを小さく出来ます。

  • mbed-dev で NDEBUG を有効にする

アサートマクロ(MBED_ASSERT)は、ヘッダファイル内で定義され、各プラットフォームのコード中で参照され mbed ライブラリに含まれています。アサートマクロのコードをリンクさせないために、ソースコードを使用して再コンパイルする必要があります。使用しているプロジェクトの mbed ライブラリを削除し、代わりに mbed-dev ライブラリをインポートします。

コンパイルメニューの「コンパイル時マクロ」を開き、テキストフィールドに NDEBUG と入力します。

/media/uploads/MACRUM/ndebug.png

この状態で、コンパイルすると MBED_ASSERT マクロが無効になり、printf 関連のコードはリンクされません。

#ifdef NDEBUG
#define MBED_ASSERT(expr) ((void)0)

#else
#define MBED_ASSERT(expr)                                \
do {                                                     \
    if (!(expr)) {                                       \
        mbed_assert_internal(#expr, __FILE__, __LINE__); \
    }                                                    \
} while (0)
#endif

サンプルの blinky で比較してみると、10kB程度あったバイナリサイズは、2kB程度にまで小さくなっています。

/media/uploads/MACRUM/blinky_raw.png /media/uploads/MACRUM/blinky_ndebug.png

  • 空の error() 関数を定義する

mbed-dev を使用せずに mbed ライブラリを使いつつバイナリサイズを抑えることも出来ます。上記の MBED_ASSERT マクロで使用されている mbed_assert_internal() 関数からは、WEAK 定義された error() 関数が呼び出されています。WEAK 関数は、同名の WEAK なしの関数にリンク時に置き換えることが出来るので、中身が空の error() 関数定義をユーザーコード側に用意することで、コードサイズを削減することが出来ます。

#include "mbed.h"

DigitalOut myled(LED1);

int main() {
    while(1) {
        myled = 1;
        wait(0.2);
        myled = 0;
        wait(0.2);
    }
}

void error(const char* format, ...) {
}

error() 関数を呼び出す部分のコードは削除されないので、mbed-dev を使用する場合よりも劇的に小さくはなりませんが、blinky の例では 3kB 程度になりました。mbed ライブラリをそのまま使うので、こちらの方法の方が手軽だと思います。

/media/uploads/MACRUM/blinky_add_blank_error.png

以上です。

明日は じぇーけーそふとさんです。よろしくお願いします。


Please log in to post comments.