diff --git a/Makefile b/Makefile index 1eb2aa4..9d05fce 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ clean: rm -f $(EXAMPLES) examples/*.o format: - clang-format -i src/sparsemap.c include/sparsemap.h examples/ex_*.c tests/soak.c tests/test.c lib/common.c include/common.h + clang-format -i src/sparsemap.c include/sparsemap.h examples/ex_*.c tests/soak.c tests/test.c tests/midl.c lib/common.c include/common.h # clang-format -i include/*.h src/*.c tests/*.c tests/*.h examples/*.c %.o: src/%.c diff --git a/lib/common.c b/lib/common.c index 962ed71..1cdfff2 100644 --- a/lib/common.c +++ b/lib/common.c @@ -51,6 +51,29 @@ tsc(void) return 0; } +// get microsecond timestamp +uint64_t +msts() +{ +#ifdef _SC_MONOTONIC_CLOCK + struct timespec ts; + if (sysconf(_SC_MONOTONIC_CLOCK) > 0) { + /* A monotonic clock presents */ + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + return (uint64_t)(ts.tv_sec * 1000000 + ts.tv_nsec / 1000); + else + return 0; + } + return 0; +#else + struct timeval tv; + if (gettimeofday(&tv, NULL) == 0) + return (uint64_t)(tv.tv_sec * 1000000 + tv.tv_usec); + else + return 0; +#endif +} + double nsts(void) { diff --git a/tests/soak.c b/tests/soak.c index 058a5af..53b4cd1 100644 --- a/tests/soak.c +++ b/tests/soak.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "../include/common.h" @@ -14,10 +15,123 @@ #include "midl.c" +#define INITIAL_AMOUNT 1024 * 2 + +bool recording = true; + typedef size_t pgno_t; -#define INITIAL_AMOUNT 1024 * 2 -bool recording = true; +typedef enum { SM, ML, RB } container_impl_t; + +typedef struct container { + const char *name; + /* allocate a new container */ + void *(*alloc)(size_t capacity); + struct { + td_histogram_t *td; + void *(*alloc)(size_t capacity); + } alloc_stats; + + /* free the container */ + void (*free)(void *handle); + struct { + td_histogram_t *td; + void (*free)(void *handle); + } free_stats; + + /* add pg to the container */ + pgno_t (*set)(void **handle, pgno_t pg); + struct { + td_histogram_t *td; + pgno_t (*set)(void **handle, pgno_t pg); + } set_stats; +#define timed_set(fn) (pgno_t(*)(void **, pgno_t)) __stats_set, .set_stats.set = fn + + /* is pg in the container */ + bool (*is_set)(void *handle, pgno_t pg); + struct { + td_histogram_t *td; + bool (*is_set)(void *handle, pgno_t pg); + } is_set_stats; +#define timed_is_set(fn) (bool (*)(void *, pgno_t)) __stats_is_set, .is_set_stats.is_set = fn + + /* remove pg from the container */ + pgno_t (*clear)(void **handle, pgno_t pg); + struct { + td_histogram_t *td; + pgno_t (*clear)(void **handle, pgno_t pg); + } clear_stats; +#define timed_clear(fn) (pgno_t(*)(void **, pgno_t)) __stats_clear, .clear_stats.clear = fn + + /* find a set of contigious page of len and return the smallest pgno */ + pgno_t (*find_span)(void *handle, unsigned len); + struct { + td_histogram_t *td; + pgno_t (*find_span)(void *handle, unsigned len); + } find_span_stats; +#define timed_find_span(fn) (pgno_t(*)(void *, unsigned)) __stats_find_span, .find_span_stats.find_span = fn + + /* remove the span [pg, pg + len) from the container */ + bool (*take_span)(void **handle, pgno_t pg, unsigned len); + struct { + td_histogram_t *td; + bool (*take_span)(void **handle, pgno_t pg, unsigned len); + } take_span_stats; +#define timed_take_span(fn) (bool (*)(void **, pgno_t, unsigned)) __stats_take_span, .take_span_stats.take_span = fn + + /* add the span [pg, pg + len) into the container */ + bool (*release_span)(void **handle, pgno_t pg, unsigned len); + struct { + td_histogram_t *td; + bool (*release_span)(void **handle, pgno_t pg, unsigned len); + } release_span_stats; +#define timed_release_span(fn) (bool (*)(void **, pgno_t, unsigned)) __stats_release_span, .release_span_stats.release_span = fn + + /* are the pgno in the span [pg, pg+ len) in the container? */ + bool (*is_span)(void *handle, pgno_t pg, unsigned len); + struct { + td_histogram_t *td; + bool (*is_span)(void *handle, pgno_t pg, unsigned len); + } is_span_stats; +#define timed_is_span(fn) (bool (*)(void *, pgno_t, unsigned)) __stats_is_span, .is_span_stats.is_span = fn + + /* are the pgno in the span [pg, pg+ len) notn in the container? */ + bool (*is_empty)(void *handle, pgno_t pg, unsigned len); + struct { + td_histogram_t *td; + bool (*is_empty)(void *handle, pgno_t pg, unsigned len); + } is_empty_stats; +#define timed_is_empty(fn) (bool (*)(void *, pgno_t, unsigned)) __stats_is_empty, .is_empty_stats.is_empty = fn + + /* is the span the first one (brute force check) */ + bool (*is_first)(void *handle, pgno_t pg, unsigned len); + struct { + td_histogram_t *td; + bool (*is_first)(void *handle, pgno_t pg, unsigned len); + } is_first_stats; +#define timed_is_first(fn) (bool (*)(void *, pgno_t, unsigned)) __stats_is_first, .is_first_stats.is_first = fn + + /* ensure that all pgno contained in other_handle are also in handle */ + bool (*merge)(void **handle, void *other_handle); + struct { + td_histogram_t *td; + bool (*merge)(void **handle, void *other_handle); + } merge_stats; +#define timed_merge(fn) (bool (*)(void **, void *)) __stats_merge, .merge_stats.merge = fn + + /* the bytes size of the container */ + size_t (*size)(void *handle); + td_histogram_t *size_stats; + + /* the number of items in the container */ + size_t (*count)(void *handle); + td_histogram_t *count_stats; + + /* perform internal validation on the container (optional) */ + bool (*validate)(void *handle); + td_histogram_t *validate_stats; + +} container_t; char * bytes_as(double bytes, char *s, size_t size) @@ -200,6 +314,8 @@ b64_decode(const char *in, unsigned char *out, size_t outlen) return 1; } +/* recording ------------------------------------------------------------- */ + static void record_set_mutation(FILE *out, pgno_t pg) { @@ -462,7 +578,7 @@ __midl_set(void **handle, pgno_t pg) } mdb_midl_xappend(list, pg); mdb_midl_sort(list); - //assert(mdb_midl_insert(list, pg) == 0); + // assert(mdb_midl_insert(list, pg) == 0); assert(__midl_validate(*handle)); return pg; } @@ -631,7 +747,7 @@ __midl_validate(void *handle) if (list[i] == list[i - 1]) { return false; } - // ensure ordering + // ensure ordering if (list[i] > list[i - 1]) return false; } @@ -755,59 +871,192 @@ __roar_validate(void *handle) return true; } +/* histogram ------------------------------------------------------------- */ + +typedef struct sw { + struct timespec t1; /* start time */ + struct timespec t2; /* stop time */ +} sw_t; + +void +ts(struct timespec *ts) +{ + if (clock_gettime(CLOCK_REALTIME, ts) == -1) { + perror("clock_gettime"); + } +} + +static double +elapsed(struct timespec *s, struct timespec *e) +{ + long sec, nanos; + + sec = e->tv_sec - s->tv_sec; + nanos = e->tv_nsec - e->tv_nsec; + if (nanos < 0) { + nanos += 1e9; + sec--; + } + return ((double)nanos / 1e9 + (double)sec); +} + +static pgno_t +__stats_set(td_histogram_t *stats, void *fn, void **handle, pgno_t pg) +{ + if (stats) { + struct timespec s, e; + ts(&s); + pgno_t retval = ((pgno_t(*)(void **, pgno_t))fn)(handle, pg); + ts(&e); + td_add(stats, elapsed(&s, &e), 1); + return retval; + } + return ((pgno_t(*)(void **, pgno_t))fn)(handle, pg); +} + +static bool +__stats_is_set(td_histogram_t *stats, void *fn, void *handle, pgno_t pg) +{ + if (stats) { + struct timespec s, e; + ts(&s); + bool retval = ((bool (*)(void *, pgno_t))fn)(handle, pg); + ts(&e); + td_add(stats, elapsed(&s, &e), 1); + return retval; + } + return ((bool (*)(void *, pgno_t))fn)(handle, pg); +} + +static pgno_t +__stats_clear(td_histogram_t *stats, void *fn, void **handle, pgno_t pg) +{ + if (stats) { + struct timespec s, e; + ts(&s); + pgno_t retval = ((pgno_t(*)(void **, pgno_t))fn)(handle, pg); + ts(&e); + td_add(stats, elapsed(&s, &e), 1); + return retval; + } + return ((pgno_t(*)(void **, pgno_t))fn)(handle, pg); +} + +static pgno_t +__stats_find_span(td_histogram_t *stats, void *fn, void *handle, unsigned len) +{ + if (stats) { + struct timespec s, e; + ts(&s); + pgno_t retval = ((pgno_t(*)(void *, unsigned))fn)(handle, len); + ts(&e); + td_add(stats, elapsed(&s, &e), 1); + return retval; + } + return ((pgno_t(*)(void *, unsigned))fn)(handle, len); +} + +static bool +__stats_take_span(td_histogram_t *stats, void *fn, void **handle, pgno_t pg, unsigned len) +{ + if (stats) { + struct timespec s, e; + ts(&s); + bool retval = ((bool (*)(void *, pgno_t, unsigned))fn)(handle, pg, len); + ts(&e); + td_add(stats, elapsed(&s, &e), 1); + return retval; + } + return ((bool (*)(void *, pgno_t, unsigned))fn)(handle, pg, len); +} + +static bool +__stats_release_span(td_histogram_t *stats, void *fn, void **handle, pgno_t pg, unsigned len) +{ + if (stats) { + struct timespec s, e; + ts(&s); + bool retval = ((bool (*)(void *, pgno_t, unsigned))fn)(handle, pg, len); + ts(&e); + td_add(stats, elapsed(&s, &e), 1); + return retval; + } + return ((bool (*)(void *, pgno_t, unsigned))fn)(handle, pg, len); +} + +static bool +__stats_is_span(td_histogram_t *stats, void *fn, void *handle, pgno_t pg, unsigned len) +{ + if (stats) { + struct timespec s, e; + ts(&s); + bool retval = ((bool (*)(void *, pgno_t, unsigned))fn)(handle, pg, len); + ts(&e); + td_add(stats, elapsed(&s, &e), 1); + return retval; + } + return ((bool (*)(void *, pgno_t, unsigned))fn)(handle, pg, len); +} + +static bool +__stats_is_empty(td_histogram_t *stats, void *fn, void *handle, pgno_t pg, unsigned len) +{ + if (stats) { + struct timespec s, e; + ts(&s); + bool retval = ((bool (*)(void *, pgno_t, unsigned))fn)(handle, pg, len); + ts(&e); + td_add(stats, elapsed(&s, &e), 1); + return retval; + } + return ((bool (*)(void *, pgno_t, unsigned))fn)(handle, pg, len); +} + +static bool +__stats_is_first(td_histogram_t *stats, void *fn, void *handle, pgno_t pg, unsigned len) +{ + if (stats) { + struct timespec s, e; + ts(&s); + bool retval = ((bool (*)(void *, pgno_t, unsigned))fn)(handle, pg, len); + ts(&e); + td_add(stats, elapsed(&s, &e), 1); + return retval; + } + return ((bool (*)(void *, pgno_t, unsigned))fn)(handle, pg, len); +} + +static bool +__stats_merge(td_histogram_t *stats, void *fn, void **handle, void *other_handle) +{ + if (stats) { + struct timespec s, e; + ts(&s); + bool retval = ((bool (*)(void **, void *))fn)(handle, other_handle); + ts(&e); + td_add(stats, elapsed(&s, &e), 1); + return retval; + } + return ((bool (*)(void **, void *))fn)(handle, other_handle); +} + /* ----------------------------------------------------------------------- */ -typedef enum { SM, ML, RB } container_impl_t; - -typedef struct container { - const char *name; - /* allocate a new container */ - void *(*alloc)(size_t capacity); - /* free the container */ - void (*free)(void *handle); - /* add pg to the container */ - pgno_t (*set)(void **handle, pgno_t pg); - /* is pg in the container */ - bool (*is_set)(void *handle, pgno_t pg); - /* remove pg from the container */ - pgno_t (*clear)(void **handle, pgno_t pg); - /* find a set of contigious page of len and return the smallest pgno */ - pgno_t (*find_span)(void *handle, unsigned len); - /* remove the span [pg, pg + len) from the container */ - bool (*take_span)(void **handle, pgno_t pg, unsigned len); - /* add the span [pg, pg + len) into the container */ - bool (*release_span)(void **handle, pgno_t pg, unsigned len); - /* are the pgno in the span [pg, pg+ len) in the container? */ - bool (*is_span)(void *handle, pgno_t pg, unsigned len); - /* are the pgno in the span [pg, pg+ len) notn in the container? */ - bool (*is_empty)(void *handle, pgno_t pg, unsigned len); - /* is the span the first one (brute force check) */ - bool (*is_first)(void *handle, pgno_t pg, unsigned len); - /* ensure that all pgno contained in other_handle are also in handle */ - bool (*merge)(void **handle, void *other_handle); - /* the bytes size of the container */ - size_t (*size)(void *handle); - /* the number of items in the container */ - size_t (*count)(void *handle); - /* perform internal validation on the container (optional) */ - bool (*validate)(void *handle); -} container_t; - // clang-format off container_t containers[] = { { "sparsemap", .alloc = __sm_alloc, .free = __sm_free, - .set = __sm_set, - .is_set = __sm_is_set, - .clear = __sm_clear, - .find_span = __sm_find_span, - .take_span = __sm_take_span, - .release_span = __sm_release_span, - .is_span = __sm_is_span, - .is_empty = __sm_is_empty, - .is_first = __sm_is_first, - .merge = __sm_merge, + .set = timed_set(__sm_set), + .is_set = timed_is_set(__sm_is_set), + .clear = timed_clear(__sm_clear), + .find_span = timed_find_span(__sm_find_span), + .take_span = timed_take_span(__sm_take_span), + .release_span = timed_release_span(__sm_release_span), + .is_span = timed_is_span(__sm_is_span), + .is_empty = timed_is_empty(__sm_is_empty), + .is_first = timed_is_first(__sm_is_first), + .merge = timed_merge(__sm_merge), .size = __sm_size, .count = __sm_count, .validate = NULL @@ -815,16 +1064,16 @@ container_t containers[] = { { "midl", .alloc = __midl_alloc, .free = __midl_free, - .set = __midl_set, - .is_set = __midl_is_set, - .clear = __midl_clear, - .find_span = __midl_find_span, - .take_span = __midl_take_span, - .release_span = __midl_release_span, - .is_span = __midl_is_span, - .is_empty = __midl_is_empty, - .is_first = NULL, - .merge = __midl_merge, + .set = timed_set(__midl_set), + .is_set = timed_is_set(__midl_is_set), + .clear = timed_clear(__midl_clear), + .find_span = timed_find_span(__midl_find_span), + .take_span = timed_take_span(__midl_take_span), + .release_span = timed_release_span(__midl_release_span), + .is_span = timed_is_span(__midl_is_span), + .is_empty = timed_is_empty(__midl_is_empty), + .is_first = timed_is_first(NULL), + .merge = timed_merge(__midl_merge), .size = __midl_size, .count = __midl_count, .validate = __midl_validate @@ -832,16 +1081,16 @@ container_t containers[] = { { "roaring", .alloc = __roar_alloc, .free = __roar_free, - .set = __roar_set, - .is_set = __roar_is_set, - .clear = __roar_clear, - .find_span = __roar_find_span, - .take_span = __roar_take_span, - .release_span = __roar_release_span, - .is_span = __roar_is_span, - .is_empty = __roar_is_empty, - .is_first = NULL, - .merge = __roar_merge, + .set = timed_set(__roar_set), + .is_set = timed_is_set(__roar_is_set), + .clear = timed_clear(__roar_clear), + .find_span = timed_find_span(__roar_find_span), + .take_span = timed_take_span(__roar_take_span), + .release_span = timed_release_span(__roar_release_span), + .is_span = timed_is_span(__roar_is_span), + .is_empty = timed_is_empty(__roar_is_empty), + .is_first = timed_is_first(NULL), + .merge = timed_merge(__roar_merge), .size = __roar_size, .count = __roar_count, .validate = __roar_validate, @@ -849,6 +1098,8 @@ container_t containers[] = { }; // clang-format on +/* ----------------------------------------------------------------------- */ + void *handles[(sizeof((containers)) / sizeof((containers)[0]))]; void *new_handles[(sizeof((containers)) / sizeof((containers)[0]))]; FILE *fp; @@ -857,8 +1108,11 @@ FILE *fp; #define cast(type, fn, ...) \ if (containers[type].fn) \ containers[type].fn(handles[type], ##__VA_ARGS__) -#define invoke(type, fn, ...) containers[type].fn(handles[type], __VA_ARGS__) -#define mutate(type, fn, ...) (type == 0) ? record_##fn##_mutation(fp, __VA_ARGS__) : (void)0, containers[type].fn(&handles[type], __VA_ARGS__) + +#define invoke(type, fn, ...) __stats_##fn(containers[type].fn##_stats.td, containers[type].fn##_stats.fn, handles[type], __VA_ARGS__) +#define mutate(type, fn, ...) \ + (type == 0) ? record_##fn##_mutation(fp, __VA_ARGS__) : (void)0, \ + __stats_##fn(containers[type].fn##_stats.td, containers[type].fn##_stats.fn, &handles[type], __VA_ARGS__) #define foreach(set) for (unsigned type = 0; type < (sizeof((set)) / sizeof((set)[0])); type++) #define checkpoint(set) \ for (unsigned type = 1; type < (sizeof((set)) / sizeof((set)[0])); type++) { \ @@ -932,45 +1186,6 @@ verify_eq(unsigned a, void *ad, unsigned b, void *bd) return ret; } -td_histogram_t *l_span_loc; -td_histogram_t *b_span_loc; -td_histogram_t *l_span_take; -td_histogram_t *b_span_take; -td_histogram_t *l_span_merge; -td_histogram_t *b_span_merge; - -void -stats_header(void) -{ - printf( - "timestamp,iterations,idl_cap,idl_used,idl_bytes,sm_cap,sm_used,idl_loc_p50,idl_loc_p75,idl_loc_p90,idl_loc_p99,idl_loc_p999,sm_loc_p50,sm_loc_p75,sm_loc_p90,sm_loc_p99,sm_loc_p999,idl_take_p50,idl_take_p75,idl_take_p90,idl_take_p99,idl_take_p999,sm_take_p50,sm_take_p75,sm_take_p90,sm_take_p99,sm_take_p999,idl_merge_p50,idl_merge_p75,idl_merge_p90,idl_merge_p99,idl_merge_p999,sm_merge_p50,sm_merge_p75,sm_merge_p90,sm_merge_p99,sm_merge_p999\n"); -} - -void -stats(size_t iterations, sparsemap_t *map, MDB_IDL list) -{ - if (iterations < 10) - return; - - td_compress(l_span_loc); - td_compress(b_span_loc); - td_compress(l_span_take); - td_compress(b_span_take); - td_compress(l_span_merge); - td_compress(b_span_merge); - - printf( - "%f,%zu,%zu,%zu,%zu,%zu,%zu,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f,%.10f\n", - nsts(), iterations, list[-1], list[0], MDB_IDL_SIZEOF(list), sparsemap_get_capacity(map), sparsemap_get_size(map), td_quantile(l_span_loc, .5), - td_quantile(l_span_loc, .75), td_quantile(l_span_loc, .90), td_quantile(l_span_loc, .99), td_quantile(l_span_loc, .999), td_quantile(b_span_loc, .5), - td_quantile(b_span_loc, .75), td_quantile(b_span_loc, .90), td_quantile(b_span_loc, .99), td_quantile(b_span_loc, .999), td_quantile(l_span_take, .5), - td_quantile(l_span_take, .75), td_quantile(l_span_take, .90), td_quantile(l_span_take, .99), td_quantile(l_span_take, .999), td_quantile(b_span_take, .5), - td_quantile(b_span_take, .75), td_quantile(b_span_take, .90), td_quantile(b_span_take, .99), td_quantile(b_span_take, .999), td_quantile(l_span_merge, .5), - td_quantile(l_span_merge, .75), td_quantile(l_span_merge, .90), td_quantile(l_span_merge, .99), td_quantile(l_span_merge, .999), - td_quantile(b_span_merge, .5), td_quantile(b_span_merge, .75), td_quantile(b_span_merge, .90), td_quantile(b_span_merge, .99), - td_quantile(b_span_merge, .999)); -} - #define SHORT_OPT "r:fa:bh" #define LONG_OPT "record:,force,amount:,buffer,help" @@ -1051,6 +1266,25 @@ main(int argc, char *argv[]) unsigned types[] = { SM, ML, RB }; unsigned num_types = (sizeof((types)) / sizeof((types)[0])); + foreach(types) + { + containers[type].alloc_stats.td = NULL; + containers[type].free_stats.td = NULL; + containers[type].set_stats.td = td_new(100); + containers[type].is_set_stats.td = td_new(100); + containers[type].clear_stats.td = td_new(100); + containers[type].find_span_stats.td = td_new(100); + containers[type].take_span_stats.td = td_new(100); + containers[type].release_span_stats.td = td_new(100); + containers[type].is_span_stats.td = NULL; + containers[type].is_empty_stats.td = NULL; + containers[type].is_first_stats.td = NULL; + containers[type].merge_stats.td = td_new(100); + containers[type].size = NULL; + containers[type].count = NULL; + containers[type].validate = NULL; + } + /* Setup: add an amt of bits to each container. */ foreach(types) { @@ -1142,10 +1376,10 @@ main(int argc, char *argv[]) for (size_t i = 0; i < new_amt; i++) { // We don't want to record and we're using new_handles not // handles, so call fn directly. - assert(containers[type].is_set(handles[type], i + new_offset) == false); - assert(containers[type].is_set(new_handles[type], i + new_offset) == false); - containers[type].set(&new_handles[type], i + new_offset); - assert(containers[type].is_set(new_handles[type], i + new_offset) == true); + assert(containers[type].is_set_stats.is_set(handles[type], i + new_offset) == false); + assert(containers[type].is_set_stats.is_set(new_handles[type], i + new_offset) == false); + containers[type].set_stats.set(&new_handles[type], i + new_offset); + assert(containers[type].is_set_stats.is_set(new_handles[type], i + new_offset) == true); } } foreach(types)