大きなアライメントのメモリブロックを確保する方法

大きなアライメント(数百kbyte, 数Mbyteレベル)のブロックを確保する方法に困りました。

  • malloc()で大きめに確保して、無駄な部分を切り詰めるやり方 => 「無駄な部分」のサイズが大きすぎる。
  • posix_memalign() => これ確保したメモリはfree()できるので、余分に管理用の領域が確保されている。よって、ブロックの間に必ず隙間ができる。

後者に関しては

posix_memalign(&p, 1 << 20, 1 << 20);

を繰り返し実行してみると、

0xb7c00000
0xb7a00000
0xb7800000
0xb7600000
0xb7400000
0xb7200000
0xb7000000
0xb6e00000
0xb6c00000
0xb6a00000

というように、1Mバイト毎に確保したのに2Mバイト毎に配置されるという事になってしまいます。間の部分はもっと小さいメモリ要求の際に利用できるわけですが...。


で、GHCRTSのブロックアロケータで見つけたのが下の方法。

  1. 初回はブロックの2倍サイズでmmap()し、はみ出た部分をmunmap().
  2. 2回目以降は前回確保したブロックの次のアドレスをヒントとしてmmap()を実行。
    1. これがうまく行く確率が高いらしい。
    2. うまく行かなかったら1の方法でやりなおす。

mmap()は確保したメモリに管理用の領域を付与しないので、隙間を開けずに並べる事ができるということです。2の手順は高速化の為ですが、これがどの程度当たるかはよくわからないです。

この部分を抜き出した簡単なコード。(エラー処理などは省いてます)

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

static void *next_addr = NULL;

#define BLOCK_SIZE  (1 << 20)

static void *
alloc_block(void)
{
    size_t size, slop;
    void *ret;
    
    fprintf(stderr, "alloc_block called\n");
    size = 2 * BLOCK_SIZE;
    ret = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);

    slop = (unsigned long) ret & (BLOCK_SIZE - 1);
    munmap(ret, BLOCK_SIZE - slop);
    if (slop > 0)
        munmap(ret + size - slop, slop);
    ret += BLOCK_SIZE - slop;
    return ret;
}

int main()
{
    int i;
    for (i = 0; i < 10; ++i)
    {
        void *p;
        if (next_addr)
        {
            /* 2回め以降はアドレス直指定に挑戦 */
            p = mmap(next_addr, BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
            if ( (unsigned long)p & (BLOCK_SIZE - 1) )
            {
                /* だめだったらunmapして、真面目にやり直す */
                munmap(p, BLOCK_SIZE);
                p = alloc_block();
            }
        }
        else
            /* 初回は真面目に */
            p = alloc_block();

        printf("%p\n", p);
        next_addr = p;
    }
    return 0;
}

実行してみると

alloc_block called
0xb7c00000
0xb7b00000
0xb7a00000
0xb7900000
0xb7800000
0xb7700000
0xb7600000
0xb7500000
0xb7400000
0xb7300000

と確かに、隙間を開けずにブロックを確保できました。