#include #include #include #include #include #include #include #include #include "../include/common.h" #include "../include/roaring.h" #include "../include/sparsemap.h" #include "../include/tdigest.h" #include "midl.c" typedef size_t pgno_t; char * bytes_as(double bytes, char *s, size_t size) { const char *units[] = { "b", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB" }; size_t i = 0; while (bytes >= 1024 && i < sizeof(units) / sizeof(units[0]) - 1) { bytes /= 1024; i++; } snprintf(s, size, "%.2f %s", bytes, units[i]); return s; } /** * A "coin toss" function that is critical to the proper operation of the * Skiplist. For example, when `max = 6` this function returns 0 with * probability 0.5, 1 with 0.25, 2 with 0.125, etc. until 6 with 0.5^7. */ static int toss(size_t max) { size_t level = 0; double probability = 0.5; double random_value = (double)xorshift32() / RAND_MAX; while (random_value < probability && level < max) { level++; probability *= 0.5; } return level; } bool recording = true; void record_set_mutation(FILE *out, pgno_t pg) { if (recording) { fprintf(out, "set %lu\n", pg); } } void record_clear_mutation(FILE *out, pgno_t pg) { if (recording) { fprintf(out, "clear %lu\n", pg); } } void record_take_span_mutation(FILE *out, pgno_t pg, unsigned len) { if (recording) { fprintf(out, "take %lu %u\n", pg, len); } } void record_release_span_mutation(FILE *out, pgno_t pg, unsigned len) { if (recording) { fprintf(out, "release %lu %u\n", pg, len); } } /* sparsemap ------------------------------------------------------------- */ sparsemap_idx_t _sparsemap_merge(sparsemap_t **map, sparsemap_t *other) { do { int retval = sparsemap_merge(*map, other); if (retval != 0) { if (errno == ENOSPC) { size_t new_size = retval + (64 - (retval % 64)) + 64; *map = sparsemap_set_data_size(*map, NULL, sparsemap_get_capacity(*map) + new_size); assert(*map != NULL); errno = 0; } else { assert(false); } } else { return retval; } } while (true); } static sparsemap_idx_t _sparsemap_set(sparsemap_t **map, sparsemap_idx_t idx, bool value) { do { sparsemap_idx_t l = sparsemap_set(*map, idx, value); if (l != idx) { if (errno == ENOSPC) { *map = sparsemap_set_data_size(*map, NULL, sparsemap_get_capacity(*map) + 64); assert(*map != NULL); errno = 0; } else { assert(false); } } else { return l; } } while (true); } static void * __sm_alloc(size_t capacity) { return (void *)sparsemap(capacity); } static void __sm_free(void *handle) { sparsemap_t *map = (sparsemap_t *)handle; free(map); } static pgno_t __sm_set(void **handle, pgno_t pg) { sparsemap_t **map = (sparsemap_t **)handle; return (pgno_t)_sparsemap_set(map, pg, true); } static bool __sm_is_set(void *handle, pgno_t pg) { sparsemap_t *map = (sparsemap_t *)handle; return sparsemap_is_set(map, pg); } static pgno_t __sm_clear(void **handle, pgno_t pg) { sparsemap_t **map = (sparsemap_t **)handle; return (pgno_t)_sparsemap_set(map, pg, false); } static pgno_t __sm_find_span(void *handle, unsigned len) { sparsemap_t *map = (sparsemap_t *)handle; pgno_t pgno = (pgno_t)sparsemap_span(map, 0, len, true); assert(SPARSEMAP_NOT_FOUND(pgno) == false); return pgno; } static bool __sm_take_span(void **handle, pgno_t pg, unsigned len) { sparsemap_t **map = (sparsemap_t **)handle; for (pgno_t i = pg; i < pg + len; i++) { assert(_sparsemap_set(map, i, false) == i); } return true; } static bool __sm_release_span(void **handle, pgno_t pg, unsigned len) { sparsemap_t **map = (sparsemap_t **)handle; for (pgno_t i = pg; i <= len; i++) { assert(_sparsemap_set(map, i, true) == i); } return true; } static bool __sm_is_span(void *handle, pgno_t pg, unsigned len) { sparsemap_t *map = (sparsemap_t *)handle; for (pgno_t i = pg; i < pg + len; i++) { if (sparsemap_is_set(map, i) != true) { return false; } } return true; } static bool __sm_is_empty(void *handle, pgno_t pg, unsigned len) { sparsemap_t *map = (sparsemap_t *)handle; for (pgno_t i = 0; i < len; i++) { if (sparsemap_is_set(map, pg + i) != false) { return false; } } return true; } static bool __sm_is_first(void *handle, pgno_t pg, unsigned len) { sparsemap_t *map = (sparsemap_t *)handle; for (sparsemap_idx_t i = 0; i < pg + len; i++) { sparsemap_idx_t j = 0; while (sparsemap_is_set(map, i + j) == true && j < len) { j++; } if (j == len) { return i == pg; } } return false; } /* midl ------------------------------------------------------------------ */ static void * __midl_alloc(size_t capacity) { MDB_IDL list = mdb_midl_alloc(capacity); return (void *)list; } static void __midl_free(void *handle) { MDB_IDL list = (MDB_IDL)handle; mdb_midl_free(list); } static pgno_t __midl_set(void **handle, pgno_t pg) { MDB_IDL *_list = (MDB_IDL *)handle, list = *_list; if (list[0] + 1 == list[-1]) { mdb_midl_need(&list, list[-1] + 1); } mdb_midl_insert(list, pg); return pg; } static bool __midl_is_set(void *handle, pgno_t pg) { MDB_IDL list = (MDB_IDL)handle; pgno_t i = mdb_midl_search(list, pg); return i <= list[0] && list[i] == pg; } static pgno_t __midl_clear(void **handle, pgno_t pg) { MDB_IDL *_list = (MDB_IDL *)handle, list = *_list; unsigned len = list[0]; list[0] = len -= 1; for (unsigned j = pg - 1; j < len;) list[++j] = list[++pg]; for (unsigned j = len + 1; j <= list[-1]; j++) list[j] = 0; return pg; } static pgno_t __midl_find_span(void *handle, unsigned len) { MDB_IDL list = (MDB_IDL)handle; /* Seek a big enough contiguous page range. Prefer * pages at the tail, just truncating the list. */ int retry = 1; unsigned i = 0; pgno_t pgno = 0, *mop = list; unsigned n2 = len, mop_len = mop[0]; do { if (mop_len > n2) { i = mop_len; do { pgno = mop[i]; if (mop[i - n2] == pgno + n2) goto search_done; } while (--i > n2); if (--retry < 0) break; } } while (1); search_done:; return pgno; } static bool __midl_take_span(void **handle, pgno_t pg, unsigned len) { MDB_IDL *_list = (MDB_IDL *)handle, list = *_list; int i = list[list[0]] == pg ? list[0] : mdb_midl_search(list, pg) + len; unsigned j, num = len; pgno_t *mop = list; unsigned mop_len = mop[0]; mop[0] = mop_len -= num; /* Move any stragglers down */ for (j = i - num; j < mop_len;) mop[++j] = mop[++i]; /* Set all unused values in the array to 0 for (j = mop_len + 1; j <= mop[-1]; j++) mop[j] = 0; */ return true; } static bool __midl_release_span(void **handle, pgno_t pg, unsigned len) { MDB_IDL list = (MDB_IDL)handle; if (list[0] + len >= list[-1]) { mdb_midl_need(&list, list[-1] + len); } for (size_t i = pg; i < pg + len; i++) { mdb_midl_insert(list, i); } mdb_midl_sort(list); return true; } static bool __midl_is_span(void *handle, pgno_t pg, unsigned len) { MDB_IDL list = (MDB_IDL)handle; pgno_t idx = mdb_midl_search(list, pg); bool found = idx <= list[0] && list[idx] == pg; if (!found) return false; if (len == 1) return true; if (list[len] + 1 != list[len - 1]) return false; return true; } static bool __midl_is_empty(void *handle, pgno_t pg, unsigned len) { MDB_IDL list = (MDB_IDL)handle; for (pgno_t i = pg; i < pg + len; i++) { pgno_t idx = mdb_midl_search(list, pg); bool found = idx <= list[0] && list[idx] == pg; if (found) return false; } return true; } static bool __midl_validate(void *handle) { MDB_IDL list = (MDB_IDL)handle; pgno_t id = 1; while (id < list[0]) { if (list[id] >= list[id + 1]) return false; id++; } return true; } /* roaring --------------------------------------------------------------- */ static void * __roar_alloc(size_t capacity) { return roaring_bitmap_create(); } static void __roar_free(void *handle) { roaring_bitmap_t *rbm = (roaring_bitmap_t *)handle; roaring_free(rbm); } static pgno_t __roar_set(void **handle, pgno_t pg) { roaring_bitmap_t **_rbm = (roaring_bitmap_t **)handle, *rbm = *_rbm; assert(roaring_bitmap_add_checked(rbm, pg) == true); return pg; } static bool __roar_is_set(void *handle, pgno_t pg) { roaring_bitmap_t *rbm = (roaring_bitmap_t *)handle; return roaring_bitmap_contains(rbm, pg); } static pgno_t __roar_clear(void **handle, pgno_t pg) { roaring_bitmap_t **_rbm = (roaring_bitmap_t **)handle, *rbm = *_rbm; roaring_bitmap_remove(rbm, pg); return pg; } static pgno_t __roar_find_span(void *handle, unsigned len) { roaring_bitmap_t *rbm = (roaring_bitmap_t *)handle; uint64_t max = roaring_bitmap_maximum(rbm); uint64_t offset = roaring_bitmap_minimum(rbm); do { if (len == 1 || roaring_bitmap_range_cardinality(rbm, offset, offset + len) == len) { break; } offset++; } while (offset <= max); return offset; } static bool __roar_take_span(void **handle, pgno_t pg, unsigned len) { roaring_bitmap_t **_rbm = (roaring_bitmap_t **)handle, *rbm = *_rbm; roaring_bitmap_remove_range(rbm, pg, pg + len); roaring_bitmap_run_optimize(rbm); return true; } static bool __roar_release_span(void **handle, pgno_t pg, unsigned len) { roaring_bitmap_t **_rbm = (roaring_bitmap_t **)handle, *rbm = *_rbm; for (size_t i = pg; i < pg + len; i++) { assert(roaring_bitmap_add_checked(rbm, i) == true); } return true; } static bool __roar_is_span(void *handle, pgno_t pg, unsigned len) { roaring_bitmap_t *rbm = (roaring_bitmap_t *)handle; for (pgno_t i = pg; i < pg + len; i++) { if (roaring_bitmap_contains(rbm, i) != true) { return false; } } return true; } static bool __roar_is_empty(void *handle, pgno_t pg, unsigned len) { roaring_bitmap_t *rbm = (roaring_bitmap_t *)handle; for (pgno_t i = 0; i < len; i++) { if (roaring_bitmap_contains(rbm, pg + i) != false) { return false; } } return true; } static bool __roar_validate(void *handle) { roaring_bitmap_t *rbm = (roaring_bitmap_t *)handle; roaring_bitmap_run_optimize(rbm); return true; } /* ----------------------------------------------------------------------- */ typedef enum { SM, ML, RB } container_impl_t; typedef struct container { const char *name; void *(*alloc)(size_t capacity); void (*free)(void *handle); pgno_t (*set)(void **handle, pgno_t pg); bool (*is_set)(void *handle, pgno_t pg); pgno_t (*clear)(void **handle, pgno_t pg); pgno_t (*find_span)(void *handle, unsigned len); bool (*take_span)(void **handle, pgno_t pg, unsigned len); bool (*release_span)(void **handle, pgno_t pg, unsigned len); bool (*is_span)(void *handle, pgno_t pg, unsigned len); bool (*is_empty)(void *handle, pgno_t pg, unsigned len); bool (*is_first)(void *handle, pgno_t pg, unsigned len); 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, .validate = NULL }, { "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, .validate = __midl_validate }, { "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, .validate = __roar_validate, }, }; // clang-format on void *handles[3]; FILE *fp; #define alloc(type, size) containers[type].alloc(size); #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, ...) record_##fn##_mutation(fp, __VA_ARGS__), containers[type].fn(&handles[type], __VA_ARGS__) #define foreach(set) for (unsigned type = 0; type < (sizeof((set)) / sizeof((set)[0])); type++) #define compare(set) \ for (unsigned type = 1; type < (sizeof((set)) / sizeof((set)[0])); type++) { \ verify_eq(0, handles[0], type, handles[type]); \ } bool verify_sm_eq_rb(sparsemap_t *map, roaring_bitmap_t *rbm) { uint64_t max = roaring_bitmap_maximum(rbm); roaring_uint32_iterator_t iter; roaring_iterator_init(rbm, &iter); for (uint64_t i = 0; i <= max; i++) { if (i == iter.current_value) { assert(sparsemap_is_set(map, i) == true); roaring_uint32_iterator_advance(&iter); } else { assert(sparsemap_is_set(map, i) == false); } } return true; } bool verify_sm_eq_ml(sparsemap_t *map, MDB_IDL list) { for (MDB_ID i = 1; i <= list[0]; i++) { pgno_t pg = list[i]; unsigned skipped = i == 1 ? 0 : list[i - 1] - list[i] - 1; if (skipped) { for (MDB_ID j = list[i - 1]; j > list[i]; j--) { if (sparsemap_is_set(map, pg - j) != false) { __diag("%zu\n", pg - j); return false; } } } if (sparsemap_is_set(map, pg) != true) { __diag("%zu\n", pg); return false; } } return true; } bool verify_eq(unsigned a, void *ad, unsigned b, void *bd) { bool ret = true; // 'a' should always be a Sparsemap switch (b) { case ML: assert((ret = verify_sm_eq_ml((sparsemap_t *)ad, (MDB_IDL)bd)) == true); break; case RB: assert((ret = verify_sm_eq_rb((sparsemap_t *)ad, (roaring_bitmap_t *)bd)) == true); break; default: break; } 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 INITIAL_AMOUNT 1024 * 2 #define SHORT_OPT "r:fa:bh" #define LONG_OPT "record:,force,amount:,buffer,help" void print_usage(const char *program_name) { printf("Usage: %s [OPTIONS]\n", program_name); printf(" -r, --record Path to the file for recording (optional)\n"); printf(" -f, --force Force overwrite of existing file (optional)\n"); printf(" -b, --buffer Disable buffering writes to stdout/err (optional)\n"); printf(" -a, --amount Specify the number of entries to record (must be positive, optional)\n"); printf(" -h, --help Print this help message\n"); } int main(int argc, char *argv[]) { int opt; const char *record_file = NULL; int force_flag = 0; size_t amt = INITIAL_AMOUNT; bool buffer = true; fp = stdout; while ((opt = getopt(argc, argv, SHORT_OPT LONG_OPT)) != -1) { switch (opt) { case 'r': record_file = optarg; break; case 'f': force_flag = 1; break; case 'b': buffer = false; break; case 'a': amt = atoi(optarg); if (amt <= 0) { fprintf(stderr, "Error: Invalid amount. Amount must be a positive number.\n"); return 1; } break; case 'h': print_usage(argv[0]); return 0; case '?': fprintf(stderr, "Unknown option: %c\n", optopt); return 1; default: break; } } // Check if record file is specified if (record_file == NULL) { recording = false; } else { recording = true; // Check for existing file without force flag if (access(record_file, F_OK) == 0 && !force_flag) { fprintf(stderr, "Warning: File '%s' already exists. Use -f or --force to overwrite.\n", record_file); return 1; } // Open the file for writing (truncate if force flag is set) fp = fopen(record_file, force_flag ? "w" : "a"); if (fp == NULL) { perror("Error opening file"); return 1; } } // disable buffering if (!buffer) { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); } unsigned types[] = { SM, ML, RB }; unsigned num_types = (sizeof((types)) / sizeof((types)[0])); /* Setup: add an amt of bits to each container. */ foreach(types) { handles[type] = alloc(type, amt); for (size_t i = 0; i < amt; i++) { assert(invoke(type, is_set, i) == false); assert(mutate(type, set, i) == i); assert(invoke(type, is_set, i) == true); } cast(type, validate); } compare(types); while (true) { // the an amount [1, 16] of pages to find preferring smaller sizes unsigned len = toss(15) + 1; pgno_t loc[num_types]; foreach(types) { loc[type] = invoke(type, find_span, len); } for (unsigned n = 0; n < num_types; n++) { foreach(types) { assert(invoke(type, is_span, loc[n], len)); } } foreach(types) { cast(type, validate); } unsigned which_loc = (unsigned)xorshift32() % num_types; foreach(types) { assert(mutate(type, take_span, loc[which_loc], len)); cast(type, validate); } compare(types); } return 0; } #if 0 /* * A "soak test" that tries to replicate behavior in LMDB for page allocation. */ int main(void) { size_t replenish = 0, iterations = 0; // disable buffering #ifdef DEBUG setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); #endif l_span_loc = td_new(100); b_span_loc = td_new(100); l_span_take = td_new(100); b_span_take = td_new(100); l_span_merge = td_new(100); b_span_merge = td_new(100); stats_header(); sparsemap_idx_t amt = INITIAL_AMOUNT; MDB_IDL list = mdb_midl_alloc(amt); sparsemap_t *map = sparsemap(INITIAL_AMOUNT); roaring_bitmap_t *rbm = roaring_bitmap_create(); // start with 2GiB of 4KiB free pages to track: // - MDB_IDL requires one int for each free page // - Sparsemap will compress the set bits using less memory mdb_midl_need(&list, amt); for (sparsemap_idx_t pg = 0; pg < amt; pg++) { // We list every free (unallocated) page in the IDL, while... mdb_midl_xappend(list, pg); // ... true (unset in the bitmap) indicates free in the bitmap, ... assert(_sparsemap_set(&map, pg, true) == pg); assert(roaring_bitmap_add_checked(rbm, pg)); } mdb_midl_sort(list); roaring_bitmap_run_optimize(rbm); assert(verify_sm_eq_ml(map, list)); assert(verify_sm_eq_rm(map, rbm)); double b, e; while (1) { unsigned mi; pgno_t ml, sl, rl; // get an amount [1, 16] of pages to find preferring smaller sizes unsigned n = toss(15) + 1; // find a set of pages using the MDB_IDL { b = nsts(); /* Seek a big enough contiguous page range. Prefer * pages at the tail, just truncating the list. */ int retry = 1; unsigned i = 0; pgno_t pgno = 0, *mop = list; unsigned n2 = len, mop_len = mop[0]; if (mop_len > n2) { i = mop_len; do { pgno = mop[i]; if (mop[i - n2] == pgno + n2) goto search_done; } while (--i > n2); if (--retry < 0) break; } search_done:; ml = pgno; mi = i; e = nsts(); td_add(l_span_loc, e - b, 1); } assert(verify_span_midl(list, ml, n)); assert(verify_span_sparsemap(map, ml, n)); assert(verify_span_roaring(rbm, ml, n)); // find a set of pages using the Sparsemap { b = nsts(); pgno_t pgno = sparsemap_span(map, 0, n, true); assert(SPARSEMAP_NOT_FOUND(pgno) == false); sl = pgno; e = nsts(); td_add(b_span_loc, e - b, 1); assert(verify_sm_is_first_available_span(map, pgno, n, true)); } assert(verify_span_midl(list, sl, n)); assert(verify_span_sparsemap(map, sl, n)); assert(verify_span_roaring(rbm, sl, n)); // find a set of pages using the Roaring Bitmap { b = nsts(); uint64_t max = roaring_bitmap_maximum(rbm); uint64_t offset = roaring_bitmap_minimum(rbm); do { if (n == 1 || roaring_bitmap_range_cardinality(rbm, offset, offset + n) == n) { break; } offset++; } while (offset <= max); rl = offset; e = nsts(); } /* if (rl != sl) { assert(verify_span_midl(list, rl, n)); assert(verify_span_sparsemap(map, rl, n)); assert(verify_span_roaring(rbm, rl, n)); } */ assert(rl == sl); bool prefer_mdb_idl_loc = (bool)xorshift32() % 2; // acquire the set of pages within the list if (prefer_mdb_idl_loc) { b = nsts(); unsigned j, num = n; int i = mi; pgno_t *mop = list; unsigned mop_len = mop[0]; mop[0] = mop_len -= num; /* Move any stragglers down */ for (j = i - num; j < mop_len;) mop[++j] = mop[++i]; e = nsts(); for (j = mop_len + 1; j <= mop[-1]; j++) mop[j] = 0; td_add(l_span_take, e - b, 1); } else { b = nsts(); unsigned j, num = n; int i = mdb_midl_search(list, sl) + num; pgno_t *mop = list; unsigned mop_len = mop[0]; mop[0] = mop_len -= num; /* Move any stragglers down */ for (j = i - num; j < mop_len;) mop[++j] = mop[++i]; e = nsts(); for (j = mop_len + 1; j <= mop[-1]; j++) mop[j] = 0; td_add(l_span_take, e - b, 1); } // acquire the set of pages within the sparsemap if (prefer_mdb_idl_loc) { b = nsts(); for (pgno_t i = ml; i < ml + n; i++) { assert(_sparsemap_set(&map, i, false) == i); } e = nsts(); td_add(b_span_take, e - b, 1); } else { b = nsts(); for (pgno_t i = sl; i <= sl + n; i++) { assert(_sparsemap_set(&map, i, false) == i); } e = nsts(); td_add(b_span_take, e - b, 1); } // acquire the set of pages within the roaring bitmap if (prefer_mdb_idl_loc) { b = nsts(); roaring_bitmap_remove_range(rbm, ml, ml + n); e = nsts(); } else { b = nsts(); roaring_bitmap_remove_range(rbm, sl, sl + n); e = nsts(); } roaring_bitmap_run_optimize(rbm); assert(verify_sm_eq_ml(map, list)); assert(verify_sm_eq_rm(map, rbm)); // Once we've used a tenth of the free list, let's replenish it a bit. if (list[0] < amt / 10) { do { pgno_t pgno; size_t len, retries = amt; do { len = toss(15) + 1; pgno = sparsemap_span(map, 0, len, false); assert(verify_sm_is_first_available_span(map, pgno, n, false)); //__diag("%zu\t%zu,%zu\n", iterations, replenish, retries); } while (SPARSEMAP_NOT_FOUND(pgno) && --retries); if (retries == 0) { goto larger_please; } if (SPARSEMAP_FOUND(pgno)) { assert(verify_empty_midl(list, pgno, len)); assert(verify_empty_sparsemap(map, pgno, len)); assert(verify_empty_roaring(rbm, pgno, len)); assert(verify_sm_eq_ml(map, list)); assert(verify_sm_eq_rm(map, rbm)); if (list[-1] - list[0] < len) { mdb_midl_need(&list, list[-1] + len); } for (size_t i = pgno; i < pgno + len; i++) { assert(verify_midl_contains(list, i) == false); assert(sparsemap_is_set(map, i) == false); mdb_midl_insert(list, i); assert(verify_midl_contains(list, i) == true); assert(_sparsemap_set(&map, i, true) == i); assert(sparsemap_is_set(map, i) == true); assert(roaring_bitmap_add_checked(rbm, i) == true); } mdb_midl_sort(list); assert(verify_midl_nodups(list)); assert(verify_span_midl(list, pgno, len)); assert(verify_span_sparsemap(map, pgno, len)); assert(verify_span_roaring(rbm, pgno, len)); } assert(verify_sm_eq_ml(map, list)); assert(verify_sm_eq_rm(map, rbm)); replenish++; } while (list[0] < amt - 32); } replenish = 0; // every so often, either ... if (iterations % 1000 == 0) { larger_please:; size_t COUNT = xorshift32() % 3586 + 513; // ... add some amount of 4KiB pages, or size_t len = COUNT; // The largest page is at list[1] because this is a reverse sorted list. pgno_t pg = list[0] ? list[1] + 1 : 0; if (true) { // disable shrinking for now... (toss(6) + 1 < 7) MDB_IDL new_list = mdb_midl_alloc(len); sparsemap_t *new_map = sparsemap(INITIAL_AMOUNT); roaring_bitmap_t *new_rbm = roaring_bitmap_create(); for (size_t i = 0; i < len; i++) { pgno_t gp = (pg + len) - i; new_list[i + 1] = gp; new_list[0]++; assert(verify_midl_contains(new_list, gp) == true); assert(_sparsemap_set(&new_map, gp, true) == gp); assert(sparsemap_is_set(new_map, gp)); assert(roaring_bitmap_add_checked(new_rbm, gp)); assert(roaring_bitmap_contains(new_rbm, gp)); } assert(verify_sm_eq_ml(new_map, new_list)); assert(verify_sm_eq_rm(new_map, new_rbm)); { b = nsts(); mdb_midl_append_list(&list, new_list); mdb_midl_sort(list); e = nsts(); td_add(l_span_merge, e - b, 1); } for (size_t i = 0; i < len; i++) { pgno_t gp = (pg + len) - i; assert(verify_midl_contains(list, gp) == true); } { b = nsts(); _sparsemap_merge(&map, new_map); e = nsts(); td_add(b_span_merge, e - b, 1); } for (size_t i = 0; i < len; i++) { pgno_t gp = (pg + len) - i; assert(sparsemap_is_set(map, gp)); } free(new_map); { b = nsts(); roaring_bitmap_or_inplace(rbm, new_rbm); e = nsts(); } for (size_t i = 0; i < len; i++) { pgno_t gp = (pg + len) - i; assert(roaring_bitmap_contains(rbm, gp)); } roaring_free(new_rbm); } else { if (list[-1] > INITIAL_AMOUNT) { // ... a fraction of the time, remove COUNT / 2 of 4KiB pages. { pgno_t pg; for (size_t i = 0; i < COUNT; i++) { pg = list[list[0] - i]; assert(sparsemap_is_set(map, pg) == true); assert(_sparsemap_set(&map, pg, false) == pg); } } { roaring_bitmap_remove_range_closed(rbm, list[list[0] - COUNT], list[list[0]]); } { mdb_midl_shrink_to(&list, list[0] - COUNT); } assert(list[list[0]] != pg); assert(verify_midl_nodups(list)); verify_sm_eq_ml(map, list); verify_sm_eq_rm(map, rbm); } } } stats(iterations, map, list); // printf("\033[K%zu\r", iterations); iterations++; } return 0; } #endif