mrubyで標準のmalloc/freeじゃなく、アプリ側で用意したメモリアロケータを指定してみる。

mrubyの初期化をmrb_open()じゃなくてmrb_open_allocf()にして、引数にアロケータの関数とその関数に渡すユーザデータを与えて呼び出すことで、アプリ側でメモリ管理ができる。

アロケータは typedef void* (*mrb_allocf) (struct mrb_state *mrb, void*, size_t, void *ud); という型の関数で、サイズが0なら解放、0より大きい場合ポインタがNULLなら新規確保、NULLじゃなければ以前確保していた領域をリサイズした結果のポインタを返すことで、アプリ側で任意のメモリ管理ができる。デフォルトだと以下の様なアロケータになっている。

// src/state.c
static void*
allocf(mrb_state *mrb, void *p, size_t size, void *ud)
{
  if (size == 0) {
    free(p);
    return NULL;
  }
  else {
    return realloc(p, size);
  }
}

注意として、p == NULLかつsize == 0で呼び出されることもあるようだ。この場合はなにもせずに単にNULLを返せばよい、はず。

テストとしてmalloc/realloc/free/イミフの数を数えるアロケータを指定して、単純にmrb_load_string()に空の文字列を与えたときそれぞれ何回呼ばれるかカウントしてみた。

$ ./custom_allocator_rb
1586/1/1586/316: total=3489

同様のことをLua 5.2.1で試したところ

$ ./custom_allocator_lua
283/7/283/29: total=602

となり、mrubyが合計3489回に対しLuaが602回と、Luaのほうが呼び出し回数が少なかった。

また、最大のメモリ使用量はmrubyが185Kに対し、Luaは22Kだった。

テストのソース

#include <mruby.h>
#include <mruby/compile.h>
#include <stdlib.h>

#include <assert.h>
#include <map>
using namespace std;

map<void*, size_t> SizeMap;
int TotalSize;
int MaxSize;

int call_count;
int alloc_count, realloc_count, free_count, meanless_count;
void* allocator(mrb_state *mrb, void* ptr, size_t nsize, void *ud) {
  printf("alloc: %p, %ld\n", ptr, nsize);

  void* result;
  ++call_count;
  if (ptr == NULL) {
    if (nsize == 0) {
      result = NULL;
      ++meanless_count;
    } else {
      result = malloc(nsize);
      ++alloc_count;
      assert(SizeMap.find(result) == SizeMap.end());
      SizeMap[result] = nsize;
      TotalSize += nsize;
    }
  } else {
    if (nsize == 0) {
      free(ptr);
      result = NULL;
      ++free_count;
      assert(SizeMap.find(ptr) != SizeMap.end());
      TotalSize -= SizeMap[ptr];
      SizeMap.erase(ptr);
    } else {
      result = realloc(ptr, nsize);
      ++realloc_count;
      assert(SizeMap.find(ptr) != SizeMap.end());
      TotalSize += nsize - SizeMap[ptr];
      SizeMap.erase(ptr);
      SizeMap[result] = nsize;
    }
  }

  if (TotalSize > MaxSize)
    MaxSize = TotalSize;
  return result;
}

int main() {
  mrb_state* mrb = mrb_open_allocf(::allocator, NULL);
  mrb_load_string(mrb, "");
  mrb_close(mrb);
  printf("%d/%d/%d/%d: total=%d\n",
         alloc_count, realloc_count, free_count, meanless_count, call_count);
  printf("Size: %d, %d\n", MaxSize, TotalSize);
  return 0;
}