malloc を書く

これまでのあらすじ: GC を忘れて、スマートポインタも忘れられるソフトウェアについてぼんやり考えていたわけです。小規模のソフトならメモリは確保しっぱなしで良い、 Apache なんかなら1アクセスごとにメモリプール作って終わったら全部まとめて解放すればいい、私が書くものはそんなものが多いだろう、と。

んで、解放のこと考えなきゃいけない malloc に対して解放に関して考えることが非常に少ない環境ならやること少ないだろう malloc って頭の中で考えても結構実装めんどいしな…と malloc を見てこれは無理だと思うわけです。貧乏症には無理。なんかややこしいし。そのへんは wo さんのこのあたりとかぶりつつ。

http://d.hatena.ne.jp/w_o/20051017#p1

ではこんなわけのわからんものは使わぬ、ということでとりあえず解放を考えない、外部挿し替えを意識した (つまるところ「LD_PRELOADで呼べる」「reallocできる」の2点を満たす) malloc/realloc/calloc/free を実装してみた。

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static char* p = NULL;
static char* max = NULL;

void* malloc(size_t size) {
    size_t* r = (size_t*)(p);
    p += size+sizeof(size_t);
    if (p > max) {
        size_t step = 65536;
        while (step < size) step += step;
        /* initialize */
        if (max == NULL) {
            max = (char*)(r = (size_t*)sbrk(0));
            p = (char*)r+size+sizeof(size_t);
        }
        sbrk(step);
        max += step;
    }
    *r = size;
    return r+1;
}

void free(void* p) {}

void* realloc(void* o, size_t size) {
    void* r;
    size_t osize;
    if (!o) return malloc(size);
    osize = *((size_t*)o-1);
    if (osize >= size) return o;
    r = malloc(size);
    memcpy(r, o, size);
    return r;
}

void* calloc(size_t n, size_t s) {
    char* r = (char*)malloc(s*=n);
    memset(r, 0, s);
    return r;
}

以下ベンチマークRuby とかたぶんメモリいじり多いだろうと予想して。まぁ 14.634sec → 13.991 と速くなってる。 CGI とかに全部かましておくと少し良いかもしれない。

i@un ~/test> time ruby -e '("aaaaa".."zzzzz").each do |i|; end'
ruby -e '("aaaaa".."zzzzz").each do |i|; end'  14.62s user 0.01s system 99% cpu 14.634 total
i@un ~/test> time LD_PRELOAD=./nmalloc.so ruby -e '("aaaaa".."zzzzz").each do |i|; end'
LD_PRELOAD=./nmalloc.so ruby -e '("aaaaa".."zzzzz").each do |i|; end'  13.78s user 0.22s system 100% cpu 13.991 total

あと作っておくと便利そうなのは、

  • メモリサイズを記録しないもの (realloc が実装できない)
  • glPush/glPop 的な巻戻しができるもの

あと実装は素直だけど、 p を 1 で初期化してるのが少し不気味で、えーとこれは malloc が初回に呼出された時に初期化したいけど、その if が毎度毎度 malloc の度に呼ばれるのはウザい…というよくある問題の対処で、あとはソース読んだらわかるよねというか誰も読まないよねたぶん。

それと realloc は前回 malloc されたものを伸ばす用途が多いと思われるので、その場合のみ memcpy が発生しないように処理させると賢い…んだろうけどさっきの Ruby の例だと有意と言えるくらいの差が出なかった上に実装が面倒になるので省略。

あと、速いとかいうことが重要なんじゃないのです。何を使っているか知ってるのが重要なのです。たぶん。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h