大きなアライメントのメモリブロックを確保する方法
大きなアライメント(数百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バイト毎に配置されるという事になってしまいます。間の部分はもっと小さいメモリ要求の際に利用できるわけですが...。
で、GHCのRTSのブロックアロケータで見つけたのが下の方法。
- 初回はブロックの2倍サイズでmmap()し、はみ出た部分をmunmap().
- 2回目以降は前回確保したブロックの次のアドレスをヒントとしてmmap()を実行。
- これがうまく行く確率が高いらしい。
- うまく行かなかったら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
と確かに、隙間を開けずにブロックを確保できました。