Commit 27252580 authored by Swann Perarnau's avatar Swann Perarnau

Implement non-transparent memory interface

This is a mmap-based, non-transparent version of the library, with a
unit test checking that we can call move_pages properly from it.

No node tracking performed. Memcpy not working.
parent 449a959d
......@@ -10,10 +10,13 @@
#include <sys/mman.h>
#include <unistd.h>
#include "allocator.h"
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
const char *tmpfs = "/tmp";
#define min(a,b) ((a) < (b)? (a) : (b))
#ifndef MAX_NUMNODES 64
#define MAX_NUMNODES 64
#endif
int aml_init(int *argc, char **argv[])
{
......@@ -25,57 +28,20 @@ int aml_finalize(void)
return 0;
}
int aml_node_init(struct aml_node *node, struct bitmask *mask, size_t maxsize)
int aml_node_init(struct aml_node *node, unsigned int nid)
{
char *template, zero[4096];
size_t pos;
ssize_t count;
int fd;
int mode;
unsigned long oldmask[NUMA_NUM_NODES];
assert(node != NULL);
/* new, temporary file, to hold data */
template = calloc(1024, sizeof(char));
snprintf(template, 1024, "%s/%u.XXXXXX", tmpfs, getpid());
fd = mkstemp(template);
assert(fd != -1);
/* as weird as it sounds, using mempolicy here forces the
* future writes to end up in the right memory node.
* Only necessary on first write to a page.
* We retrieve the current policy first to restore it later
*/
assert(!get_mempolicy(&mode, oldmask, NUMA_NUM_NODES, 0, 0));
assert(!set_mempolicy(MPOL_BIND, mask->maskp, mask->size));
/* write zeros all over to pull its pages in memory*/
for(pos = 0; pos < maxsize; pos += count)
if((count = write(fd, zero, min(maxsize - pos, 4096))) <= 0)
break;
/* init internal allocator */
void *m = mmap(NULL, maxsize, PROT_READ|PROT_WRITE, MAP_PRIVATE,
fd, 0);
assert(m != MAP_FAILED);
aml_allocator_init(m, maxsize);
munmap(m, maxsize);
/* restore the original mempolicy */
assert(!set_mempolicy(mode, oldmask, NUMA_NUM_NODES));
node->path = template;
node->fd = fd;
node->maxsize = maxsize;
return 0;
assert(nid < MAX_NUMNODES);
node->numaid = nid;
node->mask = numa_bitmask_alloc(MAX_NUMNODES);
numa_bitmask_setbit(node->mask, nid);
return 0;
}
int aml_node_destroy(struct aml_node *node)
{
assert(node != NULL);
close(node->fd);
unlink(node->path);
free(node->mask);
return 0;
}
......@@ -84,59 +50,63 @@ int aml_malloc(struct aml_alloc *a, size_t memsize, size_t blocksize,
{
assert(a != NULL);
assert(memsize % blocksize == 0);
/* find one good initial pointer:
* the system will give us a start pointer so that the entire alloc can
* fit in memory.
*/
void *m = mmap(NULL, memsize, PROT_READ|PROT_WRITE, MAP_PRIVATE,
node->fd, 0);
assert(blocksize % PAGE_SIZE == 0);
/* TODO: convert to SICM */
struct bitmask *oldbind = numa_get_membind();
numa_set_membind(node->mask);
void *m = mmap(NULL, memsize, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
assert(m != MAP_FAILED);
memset(m, 0, memsize);
numa_set_membind(oldbind);
/* as long as nothing else is doing mmaps in our back, we can munmap
* and reuse the pointer immediately.
*/
munmap(m, memsize);
/* start tracking blocks */
a->start = m;
a->memsize = memsize;
a->blocksize = blocksize;
a->numblocks = memsize/blocksize;
a->nodemap = calloc(a->numblocks, sizeof(*a->nodemap));
for(unsigned long i = 0; i < a->numblocks; i++)
{
a->nodemap[i] = NULL;
aml_pull_sync(a, i, node);
}
a->nodemap[i] = node;
return 0;
}
int aml_free(struct aml_alloc *a)
{
assert(a != NULL);
free(a->nodemap);
a->nodemap = NULL;
return munmap(a->start, a->memsize);
}
int aml_pull_sync(struct aml_alloc *a, unsigned long block,
struct aml_node *node)
int aml_block_address(struct aml_alloc *a, size_t block, void **ret)
{
assert(a != NULL);
assert(block < a->numblocks);
*ret = (void*)((char*)a->start + block*a->blocksize);
return 0;
}
int aml_block_move(struct aml_alloc *a, size_t block, struct aml_node *node)
{
int flags = MAP_PRIVATE|MAP_FIXED;
int prot = PROT_READ|PROT_WRITE;
size_t offset;
void *begin, *ret;
assert(a != NULL);
assert(block < a->numblocks);
if(a->nodemap[block] != node)
{
offset = block*a->blocksize;
begin = (void*)((unsigned long)a->start + offset);
ret = mmap(begin, a->blocksize, prot, flags, node->fd, offset);
assert(ret != MAP_FAILED && ret == begin);
a->nodemap[block] = node;
if(a->nodemap[block] != node) {
unsigned long count = a->blocksize/PAGE_SIZE;
int *nodes = calloc(count, sizeof(*nodes));
void **pages = calloc(count, sizeof(*pages));
int *status = calloc(count, sizeof(*status));
for(unsigned long i = 0; i < count; i++) {
nodes[i] = node->numaid;
pages[i] = (void*)((char*)a->start + i*PAGE_SIZE);
}
move_pages(0, count, pages, nodes, status, MPOL_MF_MOVE);
}
return 0;
}
int aml_push_sync(struct aml_alloc *a, unsigned long block,
struct aml_node *node)
int aml_block_copy(struct aml_alloc *src, size_t srcblock,
struct aml_alloc *dest, size_t destblock)
{
return aml_pull_sync(a, block, node);
return 0;
}
......@@ -20,20 +20,25 @@ struct aml_alloc {
};
struct aml_node {
char *path;
int fd;
size_t maxsize;
struct bitmask *mask;
int numaid;
};
int aml_init(int *argc, char **argv[]);
int aml_finalize(void);
int aml_node_init(struct aml_node *, struct bitmask *, size_t);
int aml_node_init(struct aml_node *, unsigned int);
int aml_node_destroy(struct aml_node *);
int aml_malloc(struct aml_alloc *, size_t, size_t, struct aml_node *);
int aml_free(struct aml_alloc *);
int aml_pull_sync(struct aml_alloc *, unsigned long, struct aml_node *);
int aml_push_sync(struct aml_alloc *, unsigned long, struct aml_node *);
inline size_t aml_block_size(struct aml_alloc *a) {
return a->blocksize;
}
int aml_block_address(struct aml_alloc *, size_t, void **);
int aml_block_move(struct aml_alloc *, size_t, struct aml_node *);
int aml_block_copy(struct aml_alloc *, size_t, struct aml_alloc *, size_t);
#endif
......@@ -10,7 +10,7 @@ TESTS_ENVIRONMENT= @LIBTOOL@ --mode=execute @VALGRIND@ --tool=memcheck -q --leak
endif
# all check programs
TST_PROGS = stream_add
TST_PROGS = stream_add_pth
check_PROGRAMS = $(TST_PROGS)
TESTS = $(TST_PROGS)
#include <aml.h>
#include <assert.h>
#include <errno.h>
#include <omp.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define ITER 10
#define MEMSIZE (1UL<<20)
#define CHUNKING 4
struct aml_node slow, fast;
int kernel(unsigned long *a, unsigned long *b, unsigned long *c, size_t n)
{
size_t i;
printf("%p = %p + %p [%zi]\n",c,a,b,n);
for(i = 0; i < n; i++)
c[i] = a[i] + b[i];
return 0;
}
struct cinfo {
struct aml_alloc *tab;
pthread_t tid;
unsigned long chunk;
};
void *th_copy(void *arg)
{
struct cinfo *ci = arg;
aml_block_move(ci->tab, ci->chunk, &fast);
return arg;
}
struct winfo {
struct aml_alloc *a, *b, *c;
pthread_t *ca, *cb;
pthread_t tid;
unsigned long chunk;
};
void *th_work(void *arg)
{
struct winfo *wi = arg;
pthread_join(*(wi->ca), NULL);
pthread_join(*(wi->cb), NULL);
void *aa,*bb,*cc;
size_t esize = aml_block_size(wi->c)/sizeof(unsigned long);
aml_block_address(wi->a, wi->chunk, &aa);
aml_block_address(wi->b, wi->chunk, &bb);
aml_block_address(wi->c, wi->chunk, &cc);
printf("%p[%lu]:%p\n",wi->a->start, wi->chunk, aa);
printf("%p[%lu]:%p\n",wi->b->start, wi->chunk, bb);
printf("%p[%lu]:%p\n",wi->c->start, wi->chunk, cc);
kernel(aa, bb, cc, esize);
return arg;
}
int main(int argc, char *argv[])
{
assert(argc == 1);
aml_init(&argc, &argv);
/* we want to back our array on the slow node and use the fast node as
* a faster buffer.
*/
assert(!aml_node_init(&slow, 0));
assert(!aml_node_init(&fast, 0));
struct aml_alloc a,b,c;
/* describe the allocation */
size_t chunk_msz, esz;
int numthreads, copythreads;
/* use openmp env to figure out how many threads we want
* (we actually use 3x as much)
*/
#pragma omp parallel
{
numthreads = omp_get_num_threads();
chunk_msz = MEMSIZE/(numthreads*CHUNKING);
esz = chunk_msz/sizeof(unsigned long);
}
printf("th: %lu, mem: %zi, chunk: %zi\n",numthreads,MEMSIZE,chunk_msz);
assert(!aml_malloc(&a, MEMSIZE, chunk_msz, &slow));
assert(!aml_malloc(&b, MEMSIZE, chunk_msz, &slow));
assert(!aml_malloc(&c, MEMSIZE, chunk_msz, &fast));
/* create virtually accessible address range, backed by slow memory */
unsigned long *wa = (unsigned long*)a.start;
unsigned long *wb = (unsigned long*)b.start;
unsigned long *wc = (unsigned long*)c.start;
unsigned long esize = MEMSIZE/sizeof(unsigned long);
for(unsigned long i = 0; i < esize; i++) {
wa[i] = i;
wb[i] = esize - i;
wc[i] = 0;
}
/* run kernel */
struct cinfo *cas = calloc(numthreads, sizeof(struct cinfo));
struct cinfo *cbs = calloc(numthreads, sizeof(struct cinfo));
struct winfo *wis = calloc(numthreads, sizeof(struct winfo));
for(unsigned long i = 0; i < CHUNKING; i++) {
for(unsigned long j = 0; j < numthreads; j++) {
cas[j].tab = &a;
cas[j].chunk = i*CHUNKING + j;
cbs[j].tab = &b;
cbs[j].chunk = i*CHUNKING + j;
wis[j].a = &a;
wis[j].b = &b;
wis[j].c = &c;
wis[j].ca = &cas[j].tid;
wis[j].cb = &cbs[j].tid;
wis[j].chunk = i*CHUNKING + j;
pthread_create(&cas[j].tid, NULL, &th_copy, (void*)&cas[j]);
pthread_create(&cbs[j].tid, NULL, &th_copy, (void*)&cbs[j]);
pthread_create(&wis[j].tid, NULL, &th_work, (void*)&wis[j]);
}
for(unsigned long j = 0; j < numthreads; j++) {
pthread_join(wis[j].tid, NULL);
}
}
free(cas);
free(cbs);
free(wis);
/* validate */
for(unsigned long i = 0; i < esize; i++) {
assert(wc[i] == esize);
}
aml_free(&a);
aml_free(&b);
aml_free(&c);
aml_node_destroy(&slow);
aml_node_destroy(&fast);
aml_finalize();
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment