mrubyのメモリ使用量(リベンジ)

2014/2/20追記
以下のメモリ削減はあくまで当時のmrubyに対する変更です。今でも有効かわかりませんし、#pragma packを使うと動作しない場合もあるとかないとか、、試す方は注意ください。また当時はmrbconf.hの#define を直接編集していますが、現在はbuild_config.rb内でcc.definesを使用するのが適切です。mrubyソースのexampleが参考になります。


少し前に、chipKIT Max32上でのメモリ使用量を調べたのですが、どうも間違ってるぽいので、やり直し+メモリ(RAM)削減を試みてみました。

前回:mrubyのメモリ使用量 on chipKit Max32 - kyabの日記

測り方

chipKIT上で計測するとアップロードに時間がかかって面倒なので、Mac上のllvm-gcc 4.2 32bitで試行錯誤。

次のようなコードで検証しました。mrb_open()には、mrb_open_allocf()というメモリアロケータを指定できるバージョンがあるので、それを利用して、mrb_open()が終わるまでの総mallocサイズを計算しています。
freeされた分がわからないのですが、まぁ、雰囲気程度はつかめるだろうと。あと、mrb_open()の前と後でmalloc()で返ってくるアドレスを比較するという方法もやってみたのですが、フラグメンテーションとかがあるのか、ちゃんとした値になりませんでした。

#include <stdio.h>
#include <mruby.h>
void *heap_bottom;
size_t total_size = 0;
void *max_reached = 0;
int malloc_count = 0;

void *myallocf(struct mrb_state *mrb, void *p, size_t size, void *ud){
    if (size == 0){
        free(p);
        return NULL;
    }else{
        malloc_count++;
        total_size += size;
        void *ret = realloc(p, size);

        if (ret > max_reached){
            max_reached = ret;
        }
        return ret;
    }
}
int main(int argc, const char * argv[])
{
    heap_bottom = realloc(NULL,1);
    
    mrb_state *mrb = mrb_open_allocf(myallocf, NULL);
    mrb_close(mrb);
    
    printf("total allocated size = %lu\n",total_size);
    printf("malloc count = %d\n",malloc_count );
    printf("max memory usage = : %lu\n", (unsigned long)max_reached - (unsigned long)heap_bottom);
    return 0;
}

結果

total allocated size = 107521
malloc count = 1523
max memory usage = : 7567280

1523回malloc(またはrealloc)されて、トータル108kb位確保されていますね。max memory usageはヒープの一番下のアドレスから、確保された一番大きいアドレスまでのサイズですが、多分大きいサイズと小さいサイズの領域は離れていたりするので、約に立たない値とみなして、以降無視します。

chipKIT Max32のRAMは128kbなので、(108kb-途中でfreeされた分)が使えるわけで、実際ラジコンの自動走行までは出来たのですが、あまり大きいプログラムだと厳しいです。なので、省メモリ化をやってみました。

省メモリ化

以下のサイトを参考にしました。
mruby-NXTプロジェクトの挑戦 No.2 RAM 64kbの環境で動かされています。ソースは公開されていない様子。
mruby for embedded systems ソースも公開されていますね。

どちらも内部のハッシュのデータ構造やアルゴリズムを変えたりしていますが、mrubyの内部コードを見始めたばかりの自分にはそこまでは真似できそうにないので、表面的に手を出せそうなところだけ。ソースも置いておきます。

https://github.com/kyab/mruby_min

以下、やってみたことと、それぞれ108kbからどれだけ減ったかの経緯です。

1.MRB_USE_FLOAT

include/mrbconf.h

/* add -DMRB_USE_FLOAT to use float instead of double for floating point numbers */
#define MRB_USE_FLOAT

浮動小数点にdoubleではなくfloatを使うようにします。約3.5kb削減できました。

2.MRB_HEAP_PAGE_SIZE

include/mrbconf.h

/* number of object per heap page */
#define MRB_HEAP_PAGE_SIZE 8

ページ毎のオブジェクトサイズ(う〜ん、ページ単位でメモリ確保して、幾つかオブジェクトを入れるような内部構造なんでしょうか?)を減らします。約8.5kb削減できました。

3 コア機能の幾つかを削る

include/mrbconf.h

/* -DDISABLE_XXXX to drop the feature */
#define DISABLE_REGEXP	        /* regular expression classes */
#define DISABLE_SPRINTF	/* Kernel.sprintf method */
//#define DISABLE_MATH		/* Math functions */
#define DISABLE_TIME		/* Time class */
#define DISABLE_STRUCT	/* Struct class */
#define DISABLE_STDIO		/* use of stdio */

TIMEはどちらにせよ使えないのですが、SPRINTF,STRUCT,STDIOも削ってみました。約3.5kb削減。

4 khashのデフォルトサイズを小さく

include/mrbconf.h

/* default size of khash table bucket */
//#define KHASH_DEFAULT_SIZE 32
#define KHASH_DEFAULT_SIZE 8

内部のハッシュ?のデフォルトサイズを小さくします。これも約3.5kb削減。

5 mrb_value構造体を小さく

include/mruby/value.h

#pragma pack(1)
typedef struct mrb_value {
  union {
    mrb_float f;
    void *p;
    mrb_int i;
    mrb_sym sym;
  } value;
  enum mrb_vtype tt:8;
} mrb_value;
#pragma pack()

#pragma packで詰めます。3kb削減。

6 シンボル名の長さ制限

src/symbol.c

#pragma pack(1)
typedef struct symbol_name {
  unsigned char len;
  const char *name;
} symbol_name;
#pragma pack()

シンボル名を255文字までに制限します。3kb削減。

7 インスタンス変数の持ち方をセグメンテーションリストに。

@takahashimさんに教えてもらいました。
include/mrbconf.h

/* use segmented list for IV table */
//#define MRB_USE_IV_SEGLIST
#define MRB_USE_IV_SEGLIST

/* initial size for IV khash; ignored when MRB_USE_IV_SEGLIST is set */
//#define MRB_IVHASH_INIT_SIZE 8
#define MRB_IVHASH_INIT_SIZE 3

これは5kb削減。

ここまでを全部適用した結果です。

total allocated size = 77858
malloc count = 1458
max memory usage = : 8381936

大分減りましたね。108kb - 78kb = 30kb稼げました。これで当面いけそうです。

番外編1

mruby-NXTプロジェクトの挑戦 No.2 の(6)mrblib の isec はROMを参照する もやってみました。rom_iseqブランチにコードを置いてます。
これは、8.5kb削減と、なかなかの検討ですが、エンディアン依存のコードになってしまったので、とりあえず今は外してます。

番外編2

これも@takahashimさんに教えてもらったのですが、mrblib/*.rbを削除するという手です。これは結構強力で、40kb位削減できました。しかしこれらのファイルを削るとArrayとかKernelとかHashとか、Rubyのコアクラス達への影響が大きいのでこれもやらないことにしました。でも結局ここで使ってない機能を削るのが一番手っ取り早いですね。

chipKIT Max32上で試す

さて、メモリ削減バージョンだとどれくらいのサイズのコードがchipKIT Max32上で動くのか試します。


・・・と言いたいところですが、違う回路の実験中、配線ミスでchipKITを壊してしまいました(笑)。今2台目を注文中です・・・。