diff --git a/benchmarks/maps.c b/benchmarks/maps.c new file mode 100644 index 0000000..340d354 --- /dev/null +++ b/benchmarks/maps.c @@ -0,0 +1,433 @@ +#include + +/** + * HashMap, MultiMap and BidiMap use hashtables with different strategies. This + * benchmark is aimed to explore the pros and cons of each implementation. + * + * HashMap - A plain hashtable with no indirection using linear probing and + * robin hood hashing. + * Expectations: + * - Constant memory usage which leads to high memory usage per element + * ratio when the hashtable has few elements but the ratio is + * somewhat good as it starts to fill up; + * - Faster lookup due to no indirection and good caching; + * - Faster insertion and removal due to no calls to free or malloc. + * + * MultiMap - A hashtable using separate chaining where each bucket is a doubly + * linked list. + * Expectations: + * - High memory usage per element ratio due to the double linked + * lists; + * - Pointer indirection might lead to slower lookup and poor caching + * due to using nodes to store the key and the value; + * - A bit slower insertion and removal as calls to free and malloc are + * needed. + * + * BidiMap - Two arrays, one representing the key set and one the value set. + * Each array is made of pointers to an entry. This way one entry can + * be referenced by both the key set and the value set using only one + * node. Uses robin hood hashing to adjust the pointers. + * Expectations: + * - Overrall low memory usage compared to other implementations; + * - Pointer indirection might lead to slower lookupand poor caching + * due to using nodes to store the key and the value; + * - Very slow insertion and removal as a node needs to be added or + * removed on both arrays. + */ + +/* Generate Code */ +CMC_COLLECTION_GENERATE(HASHMAP, hmap, hmap, int, int) +CMC_COLLECTION_GENERATE(MULTIMAP, mmap, mmap, int, int) +CMC_COLLECTION_GENERATE(BIDIMAP, bmap, bmap, int, int) + +/* Forward declaration of benchmarks */ +void benchmark_bidimap(const char *collection, int total_elements, + double load_factor); +void benchmark_hashmap(const char *collection, int total_elements, + double load_factor); +void benchmark_multimap(const char *collection, int total_elements, + double load_factor); + +/* Comparator and Hash function */ +int cmp(int a, int b) +{ + return a - b; +} + +size_t hash(int a) +{ + size_t x = (size_t)a; + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = (x >> 16) ^ x; + return x; +} + +/* Custom allocators to count memory usage */ +size_t total_memory = 0; + +/* Report Functions */ +void print_overview(const char *collection, int total_elements, + double load_factor) +{ + printf("Overview\n" + " Collection : %s\n" + " Total Elements : %d\n" + " Load Factor : %.4lf\n\n", + collection, total_elements, load_factor); +} + +void print_time(const char *collection, double C, double R, double U, double D) +{ + /* C - Create, R - Read, U - Update, D - Delete */ + printf("Time [%s]: \n" + " Total : %8.0lf milliseconds \n" + " Create : %8.0lf milliseconds\n" + " Read : %8.0lf milliseconds\n" + " Update : %8.0lf milliseconds\n" + " Delete : %8.0lf milliseconds\n\n", + collection, C + R + U + D, C, R, U, D); +} + +void print_memory(const char *s, size_t memory[11], int partition_size) +{ + printf("Each Partition : %d elements\n\n", partition_size); + + for (size_t i = 0; i < 11; i++) + { + printf(" %3" PRIuMAX "%% : %12" PRIuMAX " B | %10" PRIuMAX " kB | %8" PRIuMAX " MB (+ %2.0lf%%)\n", + i * 10, memory[i], memory[i] / 1000, memory[i] / 1000000, (1 - ((double)memory[0] / (double)memory[i])) * 100.0); + } +} + +/* Custom allocation functions */ +void *my_malloc(size_t size) +{ + void *result = malloc(size); + + if (result) + total_memory += size; + + return result; +} + +void *my_calloc(size_t count, size_t size) +{ + void *result = calloc(count, size); + + if (result) + total_memory += count * size; + + return result; +} + +void *my_realloc(void *buffer, size_t size) +{ + /* This assumes that this benchmark never uses realloc */ + return realloc(buffer, size); +} + +void my_free(void *block) +{ + free(block); +} + +/* Default custom allocation node */ +struct cmc_alloc_node alloc_default = { .malloc = my_malloc, + .calloc = my_calloc, + .realloc = my_realloc, + .free = my_free }; + +int main(int argc, char const *argv[]) +{ + if (argc != 4) + { + fprintf( + stderr, + "Usage:\n" + " %s collection_name total_elements load_factor\n" + "\nOPTIONS\n" + " - collection_name : (BIDIMAP|HASHMAP|MULTIMAP|ALL)\n" + " - total_elements : Total elements to be added to the hashtable (int > 0)\n" + " - load_factor : The hashtable's load factor (0 < l < 1.0)\n", + argv[0]); + return 1; + } + + int total_elements = atoi(argv[2]); + double load_factor = atof(argv[3]); + + bool error = false; + + if (total_elements < 1) + { + error = true; + fprintf(stderr, "Invalid value for total_elements: %s\n", argv[2]); + } + + if (load_factor <= 0.0 || load_factor >= 1.0) + { + error = true; + fprintf(stderr, "Invalid value for load_factor: %s\n", argv[3]); + } + + if (error) + return 1; + + const char *collection = argv[1]; + + if (strcmp(collection, "BIDIMAP") == 0) + { + benchmark_bidimap(collection, total_elements, load_factor); + } + else if (strcmp(collection, "HASHMAP") == 0) + { + benchmark_hashmap(collection, total_elements, load_factor); + } + else if (strcmp(collection, "MULTIMAP") == 0) + { + benchmark_multimap(collection, total_elements, load_factor); + } + else if (strcmp(collection, "ALL") == 0) + { + benchmark_bidimap("BIDIMAP", total_elements, load_factor); + benchmark_hashmap("HASHMAP", total_elements, load_factor); + benchmark_multimap("MULTIMAP", total_elements, load_factor); + } + else + { + fprintf(stderr, "Unknown parameter collection_name = %s\n", collection); + return 1; + } + + return 0; +} + +void benchmark_bidimap(const char *collection, int total_elements, + double load_factor) +{ + printf("--------------------------------------------------------------------------------\n"); + + total_memory = 0; + + /* Normalize total_elements to be a multiple of 10 and calculate partition size */ + total_elements = total_elements % 10 == 0 ? total_elements : total_elements + (10 - total_elements % 10); + int partition = total_elements / 10; + + print_overview(collection, total_elements, load_factor); + + struct bmap *map = bmap_new_custom(total_elements, load_factor, cmp, hash, + cmp, hash, &alloc_default, NULL); + + /* Create, Read, Update, Delete */ + struct cmc_timer timers[4] = { 0 }; + + /* Memory Usage 0%, 10%, 20%, ... 100% */ + size_t memory[11] = {0}; + + memory[0] = total_memory; + + /* Create */ + cmc_timer_start(timers[0]); + for (int i = 0; i < 10; i++) + { + for (int j = partition * i; j < partition * (i + 1); j++) + { + bmap_insert(map, j, j); + } + + memory[i + 1] = total_memory; + } + + cmc_timer_stop(timers[0]); + cmc_timer_calc(timers[0]); + /* Create End */ + + /* Read */ + cmc_timer_start(timers[1]); + for (int i = 0; i < total_elements; i++) + bmap_contains_key(map, i); + + cmc_timer_stop(timers[1]); + cmc_timer_calc(timers[1]); + /* Read End */ + + /* Update */ + cmc_timer_start(timers[2]); + for (int i = 0; i < total_elements; i++) + bmap_update_val_by_key(map, i, total_elements - i - 1); + + cmc_timer_stop(timers[2]); + cmc_timer_calc(timers[2]); + /* Update End */ + + /* Delete */ + cmc_timer_start(timers[3]); + for (int i = 0; i < total_elements; i++) + bmap_remove_by_key(map, i, NULL, NULL); + + cmc_timer_stop(timers[3]); + cmc_timer_calc(timers[3]); + /* Delete End */ + + print_time(collection, timers[0].result, timers[1].result, timers[2].result, + timers[3].result); + + print_memory(collection, memory, partition); + + bmap_free(map, NULL); + + printf("--------------------------------------------------------------------------------\n"); +} + +void benchmark_hashmap(const char *collection, int total_elements, + double load_factor) +{ + printf("--------------------------------------------------------------------------------\n"); + + total_memory = 0; + + /* Normalize total_elements to be a multiple of 10 and calculate partition size */ + total_elements = total_elements % 10 == 0 ? total_elements : total_elements + (10 - total_elements % 10); + int partition = total_elements / 10; + + print_overview(collection, total_elements, load_factor); + + struct hmap *map = hmap_new_custom(total_elements, load_factor, cmp, hash, &alloc_default, NULL); + + /* Create, Read, Update, Delete */ + struct cmc_timer timers[4] = { 0 }; + + /* Memory Usage 0%, 10%, 20%, ... 100% */ + size_t memory[11] = {0}; + + memory[0] = total_memory; + + /* Create */ + cmc_timer_start(timers[0]); + for (int i = 0; i < 10; i++) + { + for (int j = partition * i; j < partition * (i + 1); j++) + { + hmap_insert(map, j, j); + } + + memory[i + 1] = total_memory; + } + + cmc_timer_stop(timers[0]); + cmc_timer_calc(timers[0]); + /* Create End */ + + /* Read */ + cmc_timer_start(timers[1]); + for (int i = 0; i < total_elements; i++) + hmap_contains(map, i); + + cmc_timer_stop(timers[1]); + cmc_timer_calc(timers[1]); + /* Read End */ + + /* Update */ + cmc_timer_start(timers[2]); + for (int i = 0; i < total_elements; i++) + hmap_update(map, i, total_elements - i - 1, NULL); + + cmc_timer_stop(timers[2]); + cmc_timer_calc(timers[2]); + /* Update End */ + + /* Delete */ + cmc_timer_start(timers[3]); + for (int i = 0; i < total_elements; i++) + hmap_remove(map, i, NULL); + + cmc_timer_stop(timers[3]); + cmc_timer_calc(timers[3]); + /* Delete End */ + + print_time(collection, timers[0].result, timers[1].result, timers[2].result, + timers[3].result); + + print_memory(collection, memory, partition); + + hmap_free(map, NULL); + + printf("--------------------------------------------------------------------------------\n"); +} + +void benchmark_multimap(const char *collection, int total_elements, + double load_factor) +{ + printf("--------------------------------------------------------------------------------\n"); + + total_memory = 0; + + /* Normalize total_elements to be a multiple of 10 and calculate partition size */ + total_elements = total_elements % 10 == 0 ? total_elements : total_elements + (10 - total_elements % 10); + int partition = total_elements / 10; + + print_overview(collection, total_elements, load_factor); + + struct mmap *map = mmap_new_custom(total_elements, load_factor, cmp, hash, &alloc_default, NULL); + + /* Create, Read, Update, Delete */ + struct cmc_timer timers[4] = { 0 }; + + /* Memory Usage 0%, 10%, 20%, ... 100% */ + size_t memory[11] = {0}; + + memory[0] = total_memory; + + /* Create */ + cmc_timer_start(timers[0]); + for (int i = 0; i < 10; i++) + { + for (int j = partition * i; j < partition * (i + 1); j++) + { + mmap_insert(map, j, j); + } + + memory[i + 1] = total_memory; + } + + cmc_timer_stop(timers[0]); + cmc_timer_calc(timers[0]); + /* Create End */ + + /* Read */ + cmc_timer_start(timers[1]); + for (int i = 0; i < total_elements; i++) + mmap_contains(map, i); + + cmc_timer_stop(timers[1]); + cmc_timer_calc(timers[1]); + /* Read End */ + + /* Update */ + cmc_timer_start(timers[2]); + for (int i = 0; i < total_elements; i++) + mmap_update(map, i, total_elements - i - 1, NULL); + + cmc_timer_stop(timers[2]); + cmc_timer_calc(timers[2]); + /* Update End */ + + /* Delete */ + cmc_timer_start(timers[3]); + for (int i = 0; i < total_elements; i++) + mmap_remove(map, i, NULL); + + cmc_timer_stop(timers[3]); + cmc_timer_calc(timers[3]); + /* Delete End */ + + print_time(collection, timers[0].result, timers[1].result, timers[2].result, + timers[3].result); + + print_memory(collection, memory, partition); + + mmap_free(map, NULL); + + printf("--------------------------------------------------------------------------------\n"); +}