diff --git a/Makefile b/Makefile index 499f902..4d4d2ab 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,9 @@ VERSION = 1 CC = gcc -CFLAGS = -Wall -O3 -Werror -m32 +CFLAGS = -Wall -O3 -Werror -m32 -pthread -std=gnu11 # for debugging -#CFLAGS = -Wall -g -Werror -m32 +#CFLAGS = -Wall -g -Werror -m32 -pthread -std=gnu11 SHARED_OBJS = mdriver.o memlib.o fsecs.o fcyc.o clock.o ftimer.o list.o OBJS = $(SHARED_OBJS) mm.o @@ -23,7 +23,7 @@ mdriver-gback: $(GBACK_IMPL_OBJS) $(CC) $(CFLAGS) -o $@ $(GBACK_IMPL_OBJS) mdriver.o: mdriver.c fsecs.h fcyc.h clock.h memlib.h config.h mm.h -memlib.o: memlib.c memlib.h +memlib.o: memlib.c memlib.h config.h mm.o: mm.c mm.h memlib.h fsecs.o: fsecs.c fsecs.h config.h fcyc.o: fcyc.c fcyc.h diff --git a/config.h b/config.h index f88e24c..11ad08c 100644 --- a/config.h +++ b/config.h @@ -64,13 +64,13 @@ /* * Maximum heap size in bytes */ -#define MAX_HEAP (20*(1<<20)) /* 20 MB */ +#define MAX_HEAP (1024*(1<<20)) /* 1024 MB */ /***************************************************************************** * Set exactly one of these USE_xxx constants to "1" to select a timing method *****************************************************************************/ -#define USE_FCYC 1 /* cycle counter w/K-best scheme (x86 & Alpha only) */ +//#define USE_FCYC 1 /* cycle counter w/K-best scheme (x86 & Alpha only) */ #define USE_ITIMER 0 /* interval timer (any Unix box) */ -#define USE_GETTOD 0 /* gettimeofday (any Unix box) */ +#define USE_GETTOD 1 /* gettimeofday (any Unix box) */ #endif /* __CONFIG_H */ diff --git a/mdriver.c b/mdriver.c index 27f7782..3817494 100644 --- a/mdriver.c +++ b/mdriver.c @@ -15,6 +15,9 @@ #include #include #include +#include +#include +#include #include "mm.h" #include "memlib.h" @@ -62,16 +65,6 @@ typedef struct { size_t *block_sizes; /* ... and a corresponding array of payload sizes */ } trace_t; -/* - * Holds the params to the xxx_speed functions, which are timed by fcyc. - * This struct is necessary because fcyc accepts only a pointer array - * as input. - */ -typedef struct { - trace_t *trace; - range_t *ranges; -} speed_t; - /* Summarizes the important stats for some malloc function on some trace */ typedef struct { /* defined for both libc malloc and student malloc package (mm.c) */ @@ -90,7 +83,6 @@ typedef struct { *******************/ int verbose = 0; /* global flag for verbose output */ static int errors = 0; /* number of errs found when running student malloc */ -char msg[MAXLINE]; /* for whenever we need to compose an error message */ /* Directory where default tracefiles are found */ static char tracedir[MAXLINE] = TRACEDIR; @@ -108,11 +100,12 @@ static char *default_tracefiles[] = { /* these functions manipulate range lists */ static int add_range(range_t **ranges, char *lo, int size, int tracenum, int opnum); +static __thread int check_heap_bounds; /* if off, do not check if ranges are within heap bounds */ static void remove_range(range_t **ranges, char *lo); static void clear_ranges(range_t **ranges); /* These functions read, allocate, and free storage for traces */ -static trace_t *read_trace(char *tracedir, char *filename); +static trace_t *read_trace(char *tracedir, char *filename, int verbose); static void free_trace(trace_t *trace); /* Routines for evaluating the correctness and speed of libc malloc */ @@ -121,9 +114,23 @@ static void eval_libc_speed(void *ptr); /* Routines for evaluating correctnes, space utilization, and speed of the student's malloc package in mm.c */ +static int reset_heap(int tracenum); static int eval_mm_valid(trace_t *trace, int tracenum, range_t **ranges); -static double eval_mm_util(trace_t *trace, int tracenum, range_t **ranges); +static int eval_mm_valid_inner(trace_t *trace, int tracenum, range_t **ranges); +struct single_run_args_for_valid { + char * tracefilename; + int tracenum; + pthread_barrier_t *go; +}; +struct thread_run_result { + double secs; // number of seconds used + int heapsize; // size to which memlib heap grew +}; +static void * eval_mm_valid_single(void *); +static int eval_mm_util(trace_t *trace, int tracenum, range_t **ranges); static void eval_mm_speed(void *ptr); +static void eval_mm_speed_inner(void *ptr); +static void * eval_mm_speed_single(void *_args); /* Various helper routines */ static void printresults(int n, char ** tracefiles, stats_t *stats); @@ -132,6 +139,7 @@ static void usage(void); static void unix_error(char *msg); static void malloc_error(int tracenum, int opnum, char *msg); static void app_error(char *msg); +static FILE * open_jsonfile(const char * filename_mask); /************** * Main routine @@ -146,10 +154,10 @@ int main(int argc, char **argv) range_t *ranges = NULL; /* keeps track of block extents for one trace */ stats_t *libc_stats = NULL;/* libc stats for each trace */ stats_t *mm_stats = NULL; /* mm (i.e. student) stats for each trace */ - speed_t speed_params; /* input parameters to the xx_speed routines */ int team_check = 1; /* If set, check team structure (reset by -a) */ int run_libc = 0; /* If set, run libc malloc (set by -l) */ + int nthreads = 0; /* If set to > 0, number of threads for multi-threaded testing. */ int autograder = 0; /* If set, emit summary info for autograder (-g) */ int use_mmap = 0; /* If set, have memlib use mmap() instead malloc() */ @@ -160,7 +168,7 @@ int main(int argc, char **argv) /* * Read and interpret the command line arguments */ - while ((c = getopt(argc, argv, "nf:t:hvVgal")) != EOF) { + while ((c = getopt(argc, argv, "nf:t:hvVgalm:")) != EOF) { switch (c) { case 'n': use_mmap = 1; @@ -195,6 +203,9 @@ int main(int argc, char **argv) case 'V': /* Be more verbose than -v */ verbose = 2; break; + case 'm': /* Include multi-threaded testing */ + nthreads = atoi(optarg); + break; case 'h': /* Print this message */ usage(); exit(0); @@ -257,16 +268,15 @@ int main(int argc, char **argv) /* Evaluate the libc malloc package using the K-best scheme */ for (i=0; i < num_tracefiles; i++) { - trace = read_trace(tracedir, tracefiles[i]); + trace = read_trace(tracedir, tracefiles[i], verbose > 1); libc_stats[i].ops = trace->num_ops; if (verbose > 1) printf("Checking libc malloc for correctness, "); libc_stats[i].valid = eval_libc_valid(trace, i); if (libc_stats[i].valid) { - speed_params.trace = trace; if (verbose > 1) printf("and performance.\n"); - libc_stats[i].secs = fsecs(eval_libc_speed, &speed_params); + libc_stats[i].secs = fsecs(eval_libc_speed, trace); } free_trace(trace); } @@ -284,30 +294,105 @@ int main(int argc, char **argv) if (verbose > 1) printf("\nTesting mm malloc\n"); - /* Allocate the mm stats array, with one stats_t struct per tracefile */ - mm_stats = (stats_t *)calloc(num_tracefiles, sizeof(stats_t)); + /* Allocate the mm stats array, with two stats_t struct per tracefile */ + mm_stats = (stats_t *)calloc((nthreads > 0 ? 2 : 1) * num_tracefiles, sizeof(stats_t)); if (mm_stats == NULL) unix_error("mm_stats calloc in main failed"); /* Initialize the simulated memory system in memlib.c */ mem_init(use_mmap); + int max_total_size = 0; /* Evaluate student's mm malloc package using the K-best scheme */ for (i=0; i < num_tracefiles; i++) { - trace = read_trace(tracedir, tracefiles[i]); + trace = read_trace(tracedir, tracefiles[i], verbose > 1); mm_stats[i].ops = trace->num_ops; if (verbose > 1) printf("Checking mm_malloc for correctness, "); + check_heap_bounds = 1; mm_stats[i].valid = eval_mm_valid(trace, i, &ranges); if (mm_stats[i].valid) { if (verbose > 1) printf("efficiency, "); - mm_stats[i].util = eval_mm_util(trace, i, &ranges); - speed_params.trace = trace; - speed_params.ranges = ranges; + + max_total_size = eval_mm_util(trace, i, &ranges); + mm_stats[i].util = ((double)max_total_size / (double)mem_heapsize()); if (verbose > 1) printf("and performance.\n"); - mm_stats[i].secs = fsecs(eval_mm_speed, &speed_params); + mm_stats[i].secs = fsecs(eval_mm_speed, trace); + } + + /* Test multithreaded behavior */ + if (nthreads) { + if (verbose > 1) + printf("Checking multithreaded mm_malloc for correctness\n"); + stats_t * ms = &mm_stats[i+num_tracefiles]; + ms->ops = trace->num_ops * nthreads; + + struct single_run_args_for_valid args[nthreads]; + pthread_t threads[nthreads]; + pthread_barrier_t go; + if (pthread_barrier_init(&go, NULL, nthreads)) { + perror("pthread_barrier_init"); + abort(); + } + reset_heap(i); + + for (int j = 0; j < nthreads; j++) { + args[j].go = &go; + args[j].tracefilename = tracefiles[i]; + args[j].tracenum = i; + if (pthread_create(threads + j, NULL, eval_mm_valid_single, args + j)) + perror("pthread_create"), exit(-1); + } + + ms->valid = 1; + for (int j = 0; j < nthreads; j++) { + uintptr_t this_run_valid; + if (pthread_join(threads[j], (void **)&this_run_valid)) + perror("pthread_join"), exit(-1); + + if (!this_run_valid) + ms->valid = 0; + } + if (verbose > 1) + printf("Result appears to be valid.\n"); + + if (!ms->valid) { + printf("Result is not valid, skipping further multithreads tests.\n"); + } else { + // we know max_total_size, the total amount of heap memory may vary. + // benchmark it a few times and take the average of utilization and speed. + const int REPEATS = 2; + long heap_size_avg = 0; + double runtime_avg = 0.0; + for (int k = 0; k < REPEATS; k++) { + reset_heap(i); + + for (int j = 0; j < nthreads; j++) { + args[j].go = &go; + args[j].tracefilename = tracefiles[i]; + args[j].tracenum = i; + if (pthread_create(threads + j, NULL, eval_mm_speed_single, args + j)) + perror("pthread_create"), exit(-1); + } + + for (int j = 0; j < nthreads; j++) { + struct thread_run_result *r; + if (pthread_join(threads[j], (void **) &r)) + perror("pthread_join"), exit(-1); + if (r) { + runtime_avg += r->secs; + heap_size_avg += r->heapsize; + free(r); + } + } + } + runtime_avg /= REPEATS; + heap_size_avg /= REPEATS; + ms->util = ((double)nthreads * max_total_size) / heap_size_avg; + ms->secs = runtime_avg; + } } free_trace(trace); } @@ -319,6 +404,12 @@ int main(int argc, char **argv) printf("\n"); } + if (nthreads && verbose) { + printf("\nResults for multi-threaded mm malloc:\n"); + printresults(num_tracefiles, tracefiles, mm_stats+num_tracefiles); + printf("\n"); + } + /* * Accumulate the aggregate statistics for the student's mm package */ @@ -368,10 +459,7 @@ int main(int argc, char **argv) } /* Write results to JSON file for submission */ - char jsonfilename[80]; - snprintf(jsonfilename, sizeof jsonfilename, "results.%d.json", getpid()); - printf("Writing results to %s for submission to the scoreboard\n", jsonfilename); - FILE *json = fopen(jsonfilename, "w"); + FILE * json = open_jsonfile("results.%d.json"); fprintf(json, "{"); fprintf(json, " \"results\": "); printresults_as_json(json, num_tracefiles, tracefiles, mm_stats); @@ -380,12 +468,25 @@ int main(int argc, char **argv) "{ \"avg_mm_util\": %f, \"avg_mm_throughput\" : %f, \"perfindex\": %f, \"AVG_LIBC_THRUPUT\": %f }\n", avg_mm_util, avg_mm_throughput, perfindex, AVG_LIBC_THRUPUT); } + if (nthreads > 0) { + fprintf(json, ", \"mtresults\": "); + printresults_as_json(json, num_tracefiles, tracefiles, mm_stats+num_tracefiles); + } fprintf(json, "}"); fclose(json); exit(0); } +static FILE * +open_jsonfile(const char * filename_mask) +{ + char jsonfilename[80]; + snprintf(jsonfilename, sizeof jsonfilename, filename_mask, getpid()); + printf("Writing results to %s for submission to the scoreboard\n", jsonfilename); + return fopen(jsonfilename, "w"); +} + /***************************************************************** * The following routines manipulate the range list, which keeps @@ -417,8 +518,9 @@ static int add_range(range_t **ranges, char *lo, int size, } /* The payload must lie within the extent of the heap */ - if ((lo < (char *)mem_heap_lo()) || (lo > (char *)mem_heap_hi()) || - (hi < (char *)mem_heap_lo()) || (hi > (char *)mem_heap_hi())) { + if (check_heap_bounds && ( + (lo < (char *)mem_heap_lo()) || (lo > (char *)mem_heap_hi()) || + (hi < (char *)mem_heap_lo()) || (hi > (char *)mem_heap_hi()))) { sprintf(msg, "Payload (%p:%p) lies outside heap (%p:%p)", lo, hi, mem_heap_lo(), mem_heap_hi()); malloc_error(tracenum, opnum, msg); @@ -427,7 +529,7 @@ static int add_range(range_t **ranges, char *lo, int size, /* The payload must not overlap any other payloads */ for (p = *ranges; p != NULL; p = p->next) { - if ((lo >= p->lo && lo <= p-> hi) || + if ((lo >= p->lo && lo <= p->hi) || (hi >= p->lo && hi <= p->hi)) { sprintf(msg, "Payload (%p:%p) overlaps another payload (%p:%p)\n", lo, hi, p->lo, p->hi); @@ -490,8 +592,9 @@ static void clear_ranges(range_t **ranges) /* * read_trace - read a trace file and store it in memory */ -static trace_t *read_trace(char *tracedir, char *filename) +static trace_t *read_trace(char *tracedir, char *filename, int verbose) { + char msg[MAXLINE]; FILE *tracefile; trace_t *trace; char type[MAXLINE]; @@ -500,7 +603,7 @@ static trace_t *read_trace(char *tracedir, char *filename) unsigned max_index = 0; unsigned op_index; - if (verbose > 1) + if (verbose) printf("Reading tracefile: %s\n", filename); /* Allocate the trace record */ @@ -590,10 +693,32 @@ void free_trace(trace_t *trace) * and throughput of the libc and mm malloc packages. **********************************************************************/ +static int +reset_heap(int tracenum) +{ + /* Reset the heap and free any records in the range list */ + mem_reset_brk(); + + /* Call the mm package's init function */ + if (mm_init() < 0) { + malloc_error(tracenum, 0, "mm_init failed."); + return 0; + } + return 1; +} + /* * eval_mm_valid - Check the mm malloc package for correctness */ static int eval_mm_valid(trace_t *trace, int tracenum, range_t **ranges) +{ + if (!reset_heap(tracenum)) + return 0; + + return eval_mm_valid_inner(trace, tracenum, ranges); +} + +static int eval_mm_valid_inner(trace_t *trace, int tracenum, range_t **ranges) { int i, j; int index; @@ -604,15 +729,8 @@ static int eval_mm_valid(trace_t *trace, int tracenum, range_t **ranges) char *p; /* Reset the heap and free any records in the range list */ - mem_reset_brk(); clear_ranges(ranges); - /* Call the mm package's init function */ - if (mm_init() < 0) { - malloc_error(tracenum, 0, "mm_init failed."); - return 0; - } - /* Interpret each operation in the trace in order */ for (i = 0; i < trace->num_ops; i++) { index = trace->ops[i].index; @@ -703,6 +821,28 @@ static int eval_mm_valid(trace_t *trace, int tracenum, range_t **ranges) return 1; } +static void * +eval_mm_valid_single(void *_args) +{ + struct single_run_args_for_valid * args = _args; + + /* read our own copy of this trace */ + trace_t * trace = read_trace(tracedir, args->tracefilename, 0); + + // let threads start at approximately the same moment to increase chance + // of concurrency-related failures if proper synchronization is not used. + if (pthread_barrier_wait(args->go) == PTHREAD_BARRIER_SERIAL_THREAD) { + ; + } + range_t *ranges = NULL; + check_heap_bounds = 0; + int isvalid = eval_mm_valid_inner(trace, args->tracenum, &ranges); + assert (sizeof(int) <= sizeof(void*)); + clear_ranges(&ranges); + free_trace(trace); + return (void *) isvalid; +} + /* * eval_mm_util - Evaluate the space utilization of the student's package * The idea is to remember the high water mark "hwm" of the heap for @@ -713,8 +853,9 @@ static int eval_mm_valid(trace_t *trace, int tracenum, range_t **ranges) * doesn't allow the students to decrement the brk pointer, so brk * is always the high water mark of the heap. * + * Changed to return max_total_size */ -static double eval_mm_util(trace_t *trace, int tracenum, range_t **ranges) +static int eval_mm_util(trace_t *trace, int tracenum, range_t **ranges) { int i; int index; @@ -793,9 +934,32 @@ static double eval_mm_util(trace_t *trace, int tracenum, range_t **ranges) } } - return ((double)max_total_size / (double)mem_heapsize()); + return max_total_size; } +static void * +eval_mm_speed_single(void *_args) +{ + struct single_run_args_for_valid * args = _args; + struct timeval stv, etv; + + /* read our own copy of this trace */ + trace_t * trace = read_trace(tracedir, args->tracefilename, 0); + + // we start the clock here to avoid accounting for thread startup overhead + pthread_barrier_wait(args->go); + gettimeofday(&stv, NULL); + eval_mm_speed_inner(trace); + gettimeofday(&etv,NULL); + free_trace(trace); + if (pthread_barrier_wait(args->go) == PTHREAD_BARRIER_SERIAL_THREAD) { + struct thread_run_result *r = malloc(sizeof (*r)); + r->secs = (etv.tv_sec - stv.tv_sec) + 1E-6*(etv.tv_usec-stv.tv_usec); + r->heapsize = mem_heapsize(); + return r; + } else + return NULL; +} /* * eval_mm_speed - This is the function that is used by fcyc() @@ -803,15 +967,21 @@ static double eval_mm_util(trace_t *trace, int tracenum, range_t **ranges) */ static void eval_mm_speed(void *ptr) { - int i, index, size, newsize; - char *p, *newp, *oldp, *block; - trace_t *trace = ((speed_t *)ptr)->trace; - /* Reset the heap and initialize the mm package */ mem_reset_brk(); if (mm_init() < 0) app_error("mm_init failed in eval_mm_speed"); + eval_mm_speed_inner(ptr); +} + +static void eval_mm_speed_inner(void *ptr) +{ + int i, index, size, newsize; + char *p, *newp, *oldp, *block; + const trace_t *trace = (trace_t *)ptr; + + /* Interpret each trace request */ for (i = 0; i < trace->num_ops; i++) switch (trace->ops[i].type) { @@ -898,7 +1068,7 @@ static void eval_libc_speed(void *ptr) int i; int index, size, newsize; char *p, *newp, *oldp, *block; - trace_t *trace = ((speed_t *)ptr)->trace; + trace_t *trace = (trace_t *)ptr; for (i = 0; i < trace->num_ops; i++) { switch (trace->ops[i].type) { diff --git a/memlib.c b/memlib.c index 28f75ea..398137f 100644 --- a/memlib.c +++ b/memlib.c @@ -86,7 +86,7 @@ void *mem_sbrk(int incr) if ( (incr < 0) || ((mem_brk + incr) > mem_max_addr)) { errno = ENOMEM; - fprintf(stderr, "ERROR: mem_sbrk failed. Ran out of memory...\n"); + fprintf(stderr, "ERROR: mem_sbrk(%d) failed. Ran out of memory...\n", incr); return NULL; } mem_brk += incr; diff --git a/mm-book-implicit.c b/mm-book-implicit.c index 5def0e6..f2dcef5 100644 --- a/mm-book-implicit.c +++ b/mm-book-implicit.c @@ -11,6 +11,8 @@ #include "mm.h" #include "memlib.h" +#include "mm_ts.c" + /* * If NEXT_FIT defined use next fit search, else use first fit search */ @@ -336,13 +338,15 @@ static void *find_fit(size_t asize) static void printblock(void *bp) { - size_t hsize, halloc, fsize, falloc; + size_t hsize/*, halloc, fsize, falloc*/; checkheap(0); hsize = GET_SIZE(HDRP(bp)); +/* halloc = GET_ALLOC(HDRP(bp)); fsize = GET_SIZE(FTRP(bp)); falloc = GET_ALLOC(FTRP(bp)); +*/ if (hsize == 0) { printf("%p: EOL\n", bp); diff --git a/mm-gback-implicit.c b/mm-gback-implicit.c index 7a31113..11c6a59 100644 --- a/mm-gback-implicit.c +++ b/mm-gback-implicit.c @@ -20,6 +20,8 @@ #include "mm.h" #include "memlib.h" +#include "mm_ts.c" + struct boundary_tag { int inuse:1; // inuse bit int size:31; // size of block, in words diff --git a/mm_ts.c b/mm_ts.c new file mode 100644 index 0000000..ecc5051 --- /dev/null +++ b/mm_ts.c @@ -0,0 +1,42 @@ +/* + * Thread-safety wrapper. + * To be included in mm.c - you may assume this file exists, or else + * (better!) implement your own version. + * + * Generally, #including .c files is fragile and not good style. + * This is just a stop-gap solution. + */ +#include +static pthread_mutex_t malloc_lock = PTHREAD_MUTEX_INITIALIZER; + +void *_mm_malloc_thread_unsafe(size_t size); +void _mm_free_thread_unsafe(void *bp); +void *_mm_realloc_thread_unsafe(void *ptr, size_t size); + +void *mm_malloc(size_t size) +{ + pthread_mutex_lock(&malloc_lock); + void * p = _mm_malloc_thread_unsafe(size); + pthread_mutex_unlock(&malloc_lock); + return p; +} + +void mm_free(void *bp) +{ + pthread_mutex_lock(&malloc_lock); + _mm_free_thread_unsafe(bp); + pthread_mutex_unlock(&malloc_lock); +} + +void *mm_realloc(void *ptr, size_t size) +{ + pthread_mutex_lock(&malloc_lock); + void * p = _mm_realloc_thread_unsafe(ptr, size); + pthread_mutex_unlock(&malloc_lock); + return p; +} + +#define mm_malloc _mm_malloc_thread_unsafe +#define mm_free _mm_free_thread_unsafe +#define mm_realloc _mm_realloc_thread_unsafe +