Added maps benchmark

This commit is contained in:
Leonardo committed 2019-12-04 01:05:53 -03:00
1 parent 359f282784
commit 87916ab71b
1 file changed
+433
+433
View File
@@ -0,0 +1,433 @@
#include <macro_collections.h>
/**
* 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");
}