MonetDB 内存映射mmap

在学计算机组成原理时,了解到为了平衡CPU的高速与内存慢速,在CPU与内存之间增加了L1 cache,L2 cache,以加快CPU对内存数据的访问。但同时了解到进程可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区和访问大于可用物理内存地址的缓冲区(可与磁盘之间切换)。虚拟地址通过TLB转化为物理地址,若物理地址属于内存空间,即直接访问,若不在,即需要进行磁盘切换。层级的内存架构图:

"memory" mmap将文件或者其它对象映射到内存。当从内存中读写时,就相当于读写文件中相应的字节。

mmap函数声明
1
2
   #include <sys/mman.h>
   void *mmap(void *addr, size_t len, int prot, int flag,int fileds, off_t off);

图示说明mmap: "mmap" MonetDB使用mmap实现大内存块的分配和大文件的内存映射,这样有利于数据的快速查找。因为数据不在内存,直接进行磁盘切换(虚拟地址->物理地址)。

Linux大内存的分配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  void *MT_vmalloc(size_t size, size_t *maxsize)
{
  MMAP_OPEN_DEV_ZERO;
  char *q, *r = (char *) -1L;

  if (fd < 0) {
      return NULL;
  }
  size = MT_PAGESIZE(size);
  *maxsize = MT_PAGESIZE(*maxsize);
  if (*maxsize > size) {
      r = (char *) mmap(NULL, *maxsize, PROT_NONE,
                          MMAP_FLAGS(MAP_PRIVATE | MAP_NORESERVE), MMAP_FD, 0);
  }
  if (r == (char *) -1L) {
      *maxsize = size;
      q = (char *) mmap(NULL, size, PROT_READ | PROT_WRITE,
                          MMAP_FLAGS(MAP_PRIVATE), MMAP_FD, 0);
  } else {
      q = (char *) mmap(r, size, PROT_READ | PROT_WRITE,
                          MMAP_FLAGS(MAP_PRIVATE | MAP_FIXED), MMAP_FD, 0);
  }
  MMAP_CLOSE_DEV_ZERO;
  return (void *) ((q == (char *) -1L) ? NULL : q);
}
Windows大内存的分配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 void *MT_vmalloc(size_t size, size_t *maxsize)
{
  void *p, *a = NULL;
  int mode = 0;

  size = MT_PAGESIZE(size);
  if (*maxsize < size) {
      *maxsize = size;
  }
  *maxsize = MT_SEGSIZE(*maxsize);
  if (*maxsize < 1000000) {
      mode = MEM_TOP_DOWN;    /* help NT in keeping memory defragmented */
  }
  (void) pthread_mutex_lock(&MT_mmap_lock);
  if (*maxsize > size) {
      a = (void *) VirtualAlloc(NULL, *maxsize, MEM_RESERVE | mode, PAGE_NOACCESS);
      if (a == NULL) {
          *maxsize = size;
      }
  }
  p = (void *) VirtualAlloc(a, size, MEM_COMMIT | mode, PAGE_READWRITE);
  (void) pthread_mutex_unlock(&MT_mmap_lock);
  if (p == NULL) {
      mnstr_printf(GDKstdout, "#VirtualAlloc(" PTRFMT "," SZFMT ",MEM_COMMIT,PAGE_READWRITE): failed\n", PTRFMTCAST a, size);
  }
  return p;
}

MonetDB直接使用系统的mmap,由它来进行内存与磁盘的交互。而在这基础上,在进行内存分配管理。譬如在MonetDB中有使用sql_allocator建立内存缓冲区,当缓冲区内存不够时,才向系统申请。这样能加快内存申请速度和方便管理。此种方式是使用指针数组的管理内存,而没有使用链表。这与C++STL库的空间分配器类似,对于MonetDB内部数据结构,如list,hashtable,都是使用sql_allocator来管理其申请的内存。

sql_allocator结构体
1
2
3
4
5
6
7
   typedef struct sql_allocator {
  size_t size;
  size_t nr;
  char **blks;
  size_t used;  /* memory used in last block */
  size_t usedmem;   /* used memory */
} sql_allocator;

具体的分配分三种情况处理,一:大于sz > SA_BLOCK,直接调用系统的GDKmalloc来分配;二:剩下来的内存不够分配sz > (SA_BLOCK-sa->used) 三:内存足够,只需进行必要的处理。

sa_alloc函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 #define round16(sz) ((sz+15)&~15)
char *sa_alloc( sql_allocator *sa, size_t sz )
{
  char *r;
  sz = round16(sz);
  if (sz > SA_BLOCK) {
      char *t;
      r = GDKmalloc(sz);
      if (sa->nr >= sa->size) {
          sa->size *=2;
          sa->blks = RENEW_ARRAY(char*,sa->blks,sa->size);
      }
      t = sa->blks[sa->nr-1];
      sa->blks[sa->nr-1] = r;
      sa->blks[sa->nr] = t;
      sa->nr ++;
      return r;
  }
  if (sz > (SA_BLOCK-sa->used)) {
      r = GDKmalloc(SA_BLOCK);
      if (sa->nr >= sa->size) {
          sa->size *=2;
          sa->blks = RENEW_ARRAY(char*,sa->blks,sa->size);
      }
      sa->blks[sa->nr] = r;
      sa->nr ++;
      sa->used = sz;
      return r;
  }
  r = sa->blks[sa->nr-1] + sa->used;
  sa->used += sz;
  sa->usedmem += sz;
  return r;
}

Comments