Reduce calls to malloc in unordered_map (#96)

Reduce the number of malloc calls in unordered_map. Improves
efficiency by 50% (meaning it takes half the time that it used to).
This commit is contained in:
Bailey Thompson
2020-08-14 11:43:25 -04:00
committed by GitHub
parent 8aa141785d
commit dea440d2a7
4 changed files with 108 additions and 120 deletions

View File

@@ -47,7 +47,7 @@ unordered_map unordered_map_init(size_t key_size,
/* Utility */ /* Utility */
int unordered_map_rehash(unordered_map me); int unordered_map_rehash(unordered_map me);
int unordered_map_size(unordered_map me); size_t unordered_map_size(unordered_map me);
int unordered_map_is_empty(unordered_map me); int unordered_map_is_empty(unordered_map me);
/* Accessing */ /* Accessing */
@@ -116,12 +116,12 @@ unordered_multiset_init(size_t key_size,
/* Utility */ /* Utility */
int unordered_multiset_rehash(unordered_multiset me); int unordered_multiset_rehash(unordered_multiset me);
int unordered_multiset_size(unordered_multiset me); size_t unordered_multiset_size(unordered_multiset me);
int unordered_multiset_is_empty(unordered_multiset me); int unordered_multiset_is_empty(unordered_multiset me);
/* Accessing */ /* Accessing */
int unordered_multiset_put(unordered_multiset me, void *key); int unordered_multiset_put(unordered_multiset me, void *key);
int unordered_multiset_count(unordered_multiset me, void *key); size_t unordered_multiset_count(unordered_multiset me, void *key);
int unordered_multiset_contains(unordered_multiset me, void *key); int unordered_multiset_contains(unordered_multiset me, void *key);
int unordered_multiset_remove(unordered_multiset me, void *key); int unordered_multiset_remove(unordered_multiset me, void *key);
int unordered_multiset_remove_all(unordered_multiset me, void *key); int unordered_multiset_remove_all(unordered_multiset me, void *key);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017-2019 Bailey Thompson * Copyright (c) 2017-2020 Bailey Thompson
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -40,7 +40,7 @@ unordered_map unordered_map_init(size_t key_size,
/* Utility */ /* Utility */
int unordered_map_rehash(unordered_map me); int unordered_map_rehash(unordered_map me);
int unordered_map_size(unordered_map me); size_t unordered_map_size(unordered_map me);
int unordered_map_is_empty(unordered_map me); int unordered_map_is_empty(unordered_map me);
/* Accessing */ /* Accessing */

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017-2019 Bailey Thompson * Copyright (c) 2017-2020 Bailey Thompson
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -24,26 +24,26 @@
#include <errno.h> #include <errno.h>
#include "include/unordered_map.h" #include "include/unordered_map.h"
static const int STARTING_BUCKETS = 8; #define STARTING_BUCKETS 16
static const double RESIZE_AT = 0.75; #define RESIZE_AT 0.75
static const double RESIZE_RATIO = 1.5; #define RESIZE_RATIO 2
struct internal_unordered_map { struct internal_unordered_map {
size_t key_size; size_t key_size;
size_t value_size; size_t value_size;
size_t size;
size_t capacity;
unsigned long (*hash)(const void *const key); unsigned long (*hash)(const void *const key);
int (*comparator)(const void *const one, const void *const two); int (*comparator)(const void *const one, const void *const two);
int size; char **buckets;
int capacity;
struct node **buckets;
}; };
struct node { static const size_t ptr_size = sizeof(char *);
void *key; static const size_t hash_size = sizeof(unsigned long);
void *value; static const size_t node_next_offset = 0;
unsigned long hash; static const size_t node_hash_offset = sizeof(char *);
struct node *next; static const size_t node_key_offset = sizeof(char *) + sizeof(unsigned long);
}; /* Assume the value starts right after the key ends. */
/* /*
* Gets the hash by first calling the user-defined hash, and then using a * Gets the hash by first calling the user-defined hash, and then using a
@@ -94,7 +94,7 @@ unordered_map unordered_map_init(const size_t key_size,
init->comparator = comparator; init->comparator = comparator;
init->size = 0; init->size = 0;
init->capacity = STARTING_BUCKETS; init->capacity = STARTING_BUCKETS;
init->buckets = calloc(STARTING_BUCKETS, sizeof(struct node *)); init->buckets = calloc(STARTING_BUCKETS, ptr_size);
if (!init->buckets) { if (!init->buckets) {
free(init); free(init);
return NULL; return NULL;
@@ -105,20 +105,26 @@ unordered_map unordered_map_init(const size_t key_size,
/* /*
* Adds the specified node to the map. * Adds the specified node to the map.
*/ */
static void unordered_map_add_item(unordered_map me, struct node *const add) static void unordered_map_add_item(unordered_map me, char *const add)
{ {
struct node *traverse; char *traverse;
const int index = (int) (add->hash % me->capacity); char *traverse_next;
add->next = NULL; unsigned long hash;
size_t index;
memcpy(&hash, add + node_hash_offset, hash_size);
index = hash % me->capacity;
memset(add + node_next_offset, 0, ptr_size);
if (!me->buckets[index]) { if (!me->buckets[index]) {
me->buckets[index] = add; me->buckets[index] = add;
return; return;
} }
traverse = me->buckets[index]; traverse = me->buckets[index];
while (traverse->next) { memcpy(&traverse_next, traverse + node_next_offset, ptr_size);
traverse = traverse->next; while (traverse_next) {
traverse = traverse_next;
memcpy(&traverse_next, traverse + node_next_offset, ptr_size);
} }
traverse->next = add; memcpy(traverse + node_next_offset, &add, ptr_size);
} }
/** /**
@@ -132,18 +138,21 @@ static void unordered_map_add_item(unordered_map me, struct node *const add)
*/ */
int unordered_map_rehash(unordered_map me) int unordered_map_rehash(unordered_map me)
{ {
int i; size_t i;
struct node **old_buckets = me->buckets; char **old_buckets = me->buckets;
me->buckets = calloc((size_t) me->capacity, sizeof(struct node *)); me->buckets = calloc(me->capacity, ptr_size);
if (!me->buckets) { if (!me->buckets) {
me->buckets = old_buckets; me->buckets = old_buckets;
return -ENOMEM; return -ENOMEM;
} }
for (i = 0; i < me->capacity; i++) { for (i = 0; i < me->capacity; i++) {
struct node *traverse = old_buckets[i]; char *traverse = old_buckets[i];
while (traverse) { while (traverse) {
struct node *const backup = traverse->next; char *backup;
traverse->hash = unordered_map_hash(me, traverse->key); unsigned long hash;
memcpy(&backup, traverse + node_next_offset, ptr_size);
hash = unordered_map_hash(me, traverse + node_key_offset);
memcpy(traverse + node_hash_offset, &hash, hash_size);
unordered_map_add_item(me, traverse); unordered_map_add_item(me, traverse);
traverse = backup; traverse = backup;
} }
@@ -159,7 +168,7 @@ int unordered_map_rehash(unordered_map me)
* *
* @return the size of the unordered map * @return the size of the unordered map
*/ */
int unordered_map_size(unordered_map me) size_t unordered_map_size(unordered_map me)
{ {
return me->size; return me->size;
} }
@@ -181,20 +190,21 @@ int unordered_map_is_empty(unordered_map me)
*/ */
static int unordered_map_resize(unordered_map me) static int unordered_map_resize(unordered_map me)
{ {
int i; size_t i;
const int old_capacity = me->capacity; const size_t old_capacity = me->capacity;
const int new_capacity = (int) (me->capacity * RESIZE_RATIO); const size_t new_capacity = me->capacity * RESIZE_RATIO;
struct node **old_buckets = me->buckets; char **old_buckets = me->buckets;
me->buckets = calloc((size_t) new_capacity, sizeof(struct node *)); me->buckets = calloc(new_capacity, ptr_size);
if (!me->buckets) { if (!me->buckets) {
me->buckets = old_buckets; me->buckets = old_buckets;
return -ENOMEM; return -ENOMEM;
} }
me->capacity = new_capacity; me->capacity = new_capacity;
for (i = 0; i < old_capacity; i++) { for (i = 0; i < old_capacity; i++) {
struct node *traverse = old_buckets[i]; char *traverse = old_buckets[i];
while (traverse) { while (traverse) {
struct node *const backup = traverse->next; char *backup;
memcpy(&backup, traverse + node_next_offset, ptr_size);
unordered_map_add_item(me, traverse); unordered_map_add_item(me, traverse);
traverse = backup; traverse = backup;
} }
@@ -206,41 +216,32 @@ static int unordered_map_resize(unordered_map me)
/* /*
* Determines if an element is equal to the key. * Determines if an element is equal to the key.
*/ */
static int unordered_map_is_equal(unordered_map me, static int unordered_map_is_equal(unordered_map me, char *const item,
const struct node *const item,
const unsigned long hash, const unsigned long hash,
const void *const key) const void *const key)
{ {
return item->hash == hash && me->comparator(item->key, key) == 0; unsigned long item_hash;
memcpy(&item_hash, item + node_hash_offset, hash_size);
return item_hash == hash &&
me->comparator(item + node_key_offset, key) == 0;
} }
/* /*
* Creates an element to add. * Creates an element to add.
*/ */
static struct node *unordered_map_create_element(unordered_map me, static char *unordered_map_create_element(unordered_map me,
const unsigned long hash, const unsigned long hash,
const void *const key, const void *const key,
const void *const value) const void *const value)
{ {
struct node *const init = malloc(sizeof(struct node)); char *init = malloc(ptr_size + hash_size + me->key_size);
if (!init) { if (!init) {
return NULL; return NULL;
} }
init->key = malloc(me->key_size); memset(init + node_next_offset, 0, ptr_size);
if (!init->key) { memcpy(init + node_hash_offset, &hash, hash_size);
free(init); memcpy(init + node_key_offset, key, me->key_size);
return NULL; memcpy(init + node_key_offset + me->key_size, value, me->value_size);
}
memcpy(init->key, key, me->key_size);
init->value = malloc(me->value_size);
if (!init->value) {
free(init->key);
free(init);
return NULL;
}
memcpy(init->value, value, me->value_size);
init->hash = hash;
init->next = NULL;
return init; return init;
} }
@@ -264,35 +265,41 @@ int unordered_map_put(unordered_map me, void *const key, void *const value)
{ {
const unsigned long hash = unordered_map_hash(me, key); const unsigned long hash = unordered_map_hash(me, key);
int index; int index;
if (me->size + 1 >= RESIZE_AT * me->capacity) { if (me->size + 1 >= (size_t) (RESIZE_AT * me->capacity)) {
const int rc = unordered_map_resize(me); const int rc = unordered_map_resize(me);
if (rc != 0) { if (rc != 0) {
return rc; return rc;
} }
} }
index = (int) (hash % me->capacity); index = (size_t) (hash % me->capacity);
if (!me->buckets[index]) { if (!me->buckets[index]) {
me->buckets[index] = unordered_map_create_element(me, hash, key, value); me->buckets[index] = unordered_map_create_element(me, hash, key, value);
if (!me->buckets[index]) { if (!me->buckets[index]) {
return -ENOMEM; return -ENOMEM;
} }
} else { } else {
struct node *traverse = me->buckets[index]; char *traverse = me->buckets[index];
char *traverse_next;
if (unordered_map_is_equal(me, traverse, hash, key)) { if (unordered_map_is_equal(me, traverse, hash, key)) {
memcpy(traverse->value, value, me->value_size); memcpy(traverse + node_key_offset + me->key_size, value,
me->value_size);
return 0; return 0;
} }
while (traverse->next) { memcpy(&traverse_next, traverse + node_next_offset, ptr_size);
traverse = traverse->next; while (traverse_next) {
traverse = traverse_next;
memcpy(&traverse_next, traverse + node_next_offset, ptr_size);
if (unordered_map_is_equal(me, traverse, hash, key)) { if (unordered_map_is_equal(me, traverse, hash, key)) {
memcpy(traverse->value, value, me->value_size); memcpy(traverse + node_key_offset + me->key_size, value,
me->value_size);
return 0; return 0;
} }
} }
traverse->next = unordered_map_create_element(me, hash, key, value); traverse_next = unordered_map_create_element(me, hash, key, value);
if (!traverse->next) { if (!traverse_next) {
return -ENOMEM; return -ENOMEM;
} }
memcpy(traverse + node_next_offset, &traverse_next, ptr_size);
} }
me->size++; me->size++;
return 0; return 0;
@@ -315,14 +322,14 @@ int unordered_map_put(unordered_map me, void *const key, void *const value)
int unordered_map_get(void *const value, unordered_map me, void *const key) int unordered_map_get(void *const value, unordered_map me, void *const key)
{ {
const unsigned long hash = unordered_map_hash(me, key); const unsigned long hash = unordered_map_hash(me, key);
const int index = (int) (hash % me->capacity); char *traverse = me->buckets[hash % me->capacity];
struct node *traverse = me->buckets[index];
while (traverse) { while (traverse) {
if (unordered_map_is_equal(me, traverse, hash, key)) { if (unordered_map_is_equal(me, traverse, hash, key)) {
memcpy(value, traverse->value, me->value_size); memcpy(value, traverse + node_key_offset + me->key_size,
me->value_size);
return 1; return 1;
} }
traverse = traverse->next; memcpy(&traverse, traverse + node_next_offset, ptr_size);
} }
return 0; return 0;
} }
@@ -342,13 +349,12 @@ int unordered_map_get(void *const value, unordered_map me, void *const key)
int unordered_map_contains(unordered_map me, void *const key) int unordered_map_contains(unordered_map me, void *const key)
{ {
const unsigned long hash = unordered_map_hash(me, key); const unsigned long hash = unordered_map_hash(me, key);
const int index = (int) (hash % me->capacity); char *traverse = me->buckets[hash % me->capacity];
const struct node *traverse = me->buckets[index];
while (traverse) { while (traverse) {
if (unordered_map_is_equal(me, traverse, hash, key)) { if (unordered_map_is_equal(me, traverse, hash, key)) {
return 1; return 1;
} }
traverse = traverse->next; memcpy(&traverse, traverse + node_next_offset, ptr_size);
} }
return 0; return 0;
} }
@@ -367,32 +373,31 @@ int unordered_map_contains(unordered_map me, void *const key)
*/ */
int unordered_map_remove(unordered_map me, void *const key) int unordered_map_remove(unordered_map me, void *const key)
{ {
struct node *traverse; char *traverse;
char *traverse_next;
const unsigned long hash = unordered_map_hash(me, key); const unsigned long hash = unordered_map_hash(me, key);
const int index = (int) (hash % me->capacity); const size_t index = hash % me->capacity;
if (!me->buckets[index]) { if (!me->buckets[index]) {
return 0; return 0;
} }
traverse = me->buckets[index]; traverse = me->buckets[index];
if (unordered_map_is_equal(me, traverse, hash, key)) { if (unordered_map_is_equal(me, traverse, hash, key)) {
me->buckets[index] = traverse->next; memcpy(me->buckets + index, traverse + node_next_offset, ptr_size);
free(traverse->key);
free(traverse->value);
free(traverse); free(traverse);
me->size--; me->size--;
return 1; return 1;
} }
while (traverse->next) { memcpy(&traverse_next, traverse + node_next_offset, ptr_size);
if (unordered_map_is_equal(me, traverse->next, hash, key)) { while (traverse_next) {
struct node *const backup = traverse->next; if (unordered_map_is_equal(me, traverse_next, hash, key)) {
traverse->next = traverse->next->next; memcpy(traverse + node_next_offset,
free(backup->key); traverse_next + node_next_offset, ptr_size);
free(backup->value); free(traverse_next);
free(backup);
me->size--; me->size--;
return 1; return 1;
} }
traverse = traverse->next; traverse = traverse_next;
memcpy(&traverse_next, traverse + node_next_offset, ptr_size);
} }
return 0; return 0;
} }
@@ -407,27 +412,22 @@ int unordered_map_remove(unordered_map me, void *const key)
*/ */
int unordered_map_clear(unordered_map me) int unordered_map_clear(unordered_map me)
{ {
int i; size_t i;
struct node **temp = char **updated_buckets = calloc(STARTING_BUCKETS, ptr_size);
calloc((size_t) STARTING_BUCKETS, sizeof(struct node *)); if (!updated_buckets) {
if (!temp) {
return -ENOMEM; return -ENOMEM;
} }
for (i = 0; i < me->capacity; i++) { for (i = 0; i < me->capacity; i++) {
struct node *traverse = me->buckets[i]; char *traverse = me->buckets[i];
while (traverse) { while (traverse) {
struct node *const backup = traverse; char *backup = traverse;
traverse = traverse->next; memcpy(&traverse, traverse + node_next_offset, ptr_size);
free(backup->key);
free(backup->value);
free(backup); free(backup);
} }
me->buckets[i] = NULL;
} }
me->size = 0; me->size = 0;
me->capacity = STARTING_BUCKETS; me->capacity = STARTING_BUCKETS;
free(me->buckets); me->buckets = updated_buckets;
me->buckets = temp;
return 0; return 0;
} }

View File

@@ -243,22 +243,10 @@ static void test_put_out_of_memory(void)
assert(me); assert(me);
fail_malloc = 1; fail_malloc = 1;
assert(unordered_map_put(me, &key, &value) == -ENOMEM); assert(unordered_map_put(me, &key, &value) == -ENOMEM);
fail_malloc = 1;
delay_fail_malloc = 1;
assert(unordered_map_put(me, &key, &value) == -ENOMEM);
fail_malloc = 1;
delay_fail_malloc = 2;
assert(unordered_map_put(me, &key, &value) == -ENOMEM);
assert(unordered_map_put(me, &key, &value) == 0); assert(unordered_map_put(me, &key, &value) == 0);
key = 7; key = 7;
fail_malloc = 1; fail_malloc = 1;
assert(unordered_map_put(me, &key, &value) == -ENOMEM); assert(unordered_map_put(me, &key, &value) == -ENOMEM);
fail_malloc = 1;
delay_fail_malloc = 1;
assert(unordered_map_put(me, &key, &value) == -ENOMEM);
fail_malloc = 1;
delay_fail_malloc = 2;
assert(unordered_map_put(me, &key, &value) == -ENOMEM);
assert(!unordered_map_destroy(me)); assert(!unordered_map_destroy(me));
} }
#endif #endif
@@ -269,15 +257,15 @@ static void test_resize_out_of_memory(void)
int i; int i;
unordered_map me = unordered_map_init(sizeof(int), sizeof(int), hash_int, unordered_map me = unordered_map_init(sizeof(int), sizeof(int), hash_int,
compare_int); compare_int);
for (i = 0; i < 5; i++) { for (i = 0; i < 11; i++) {
assert(unordered_map_put(me, &i, &i) == 0); assert(unordered_map_put(me, &i, &i) == 0);
} }
assert(unordered_map_size(me) == 5); assert(unordered_map_size(me) == 11);
i++; i++;
fail_calloc = 1; fail_calloc = 1;
assert(unordered_map_put(me, &i, &i) == -ENOMEM); assert(unordered_map_put(me, &i, &i) == -ENOMEM);
assert(unordered_map_size(me) == 5); assert(unordered_map_size(me) == 11);
for (i = 0; i < 5; i++) { for (i = 0; i < 11; i++) {
assert(unordered_map_contains(me, &i)); assert(unordered_map_contains(me, &i));
} }
assert(!unordered_map_destroy(me)); assert(!unordered_map_destroy(me));