This commit is contained in:
Gregory Burd 2024-05-20 14:11:33 -04:00
parent 1b4e4c5287
commit e0c79c9348
3 changed files with 373 additions and 116 deletions

View file

@ -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

View file

@ -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)
{

View file

@ -5,6 +5,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#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)