diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml new file mode 100644 index 0000000..141f4e4 --- /dev/null +++ b/.github/workflows/ccpp.yml @@ -0,0 +1,19 @@ +name: C/C++ CI + +on: [push] + +jobs: + build: + + runs-on: [ubuntu-latest] + + steps: + - uses: actions/checkout@v1 + - name: make + run: make + - name: make test + run: make test + - name: run test + run: ./test + - name: make clean + run: make clean diff --git a/LICENSE b/LICENSE index 03b6555..79502d9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,5 @@ Copyright (c) 2014 rxi - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..56293ad --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +CC = gcc +CFLAGS = -fPIC -Wall -Wextra +LDFLAGS = -shared +RM = rm -f +NAME_LIB = map +TARGET_LIB = lib$(NAME_LIB).so + +SRCS = src/map.c +TEST_SRCS = src/test.c +TEST_OUTPUT = test +OBJS = $(SRCS:.c=.o) + +.PHONY: all +all: $(TARGET_LIB) + +$(TARGET_LIB): $(OBJS) + $(CC) $(LDFLAGS) -o $@ $^ + +.PHONY: clean +clean: + -$(RM) $(TARGET_LIB) $(OBJS) $(TEST_OUTPUT) + +test: + -$(CC) $(TEST_SRCS) -l$(NAME_LIB) -Wl,-rpath,. -o $(TEST_OUTPUT) -L. diff --git a/README.md b/README.md index 805b425..ed0566e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # map A type-safe generic hashmap implementation for C. +Forked from [rxi/map](https://github.com/rxi/map), it adds the possibility to +reserve an arbitrary (power of 2) number of buckets for performance +improvements. ## Installation The [map.c](src/map.c?raw=1) and [map.h](src/map.h?raw=1) files can be dropped @@ -7,19 +10,29 @@ into an existing C project and compiled along with it. ## Usage -Before using a map it should first be initialised using the `map_init()` -function. +Before using a map, it should first be initialised using the `map_init_reserve()` +macro. +The second argument is an integer with the number of buckets to be pre-allocated. +If is not a power of 2 or 0, no pre-allocation is done. It can improve the speed but, +on the other hand, could allocate useless space. +```c +map_int_t m; +unsigned initial_nbuckets = 128; +map_init_reserve(&m, initial_nbuckets); +``` + +It pre-allocation is not needed, the macro `map_init()` can be used as well. ```c map_int_t m; map_init(&m); ``` -Values can added to a map using the `map_set()` function. +Values can added to a map using the `map_set()` macro. ```c map_set(&m, "testkey", 123); ``` -To retrieve a value from a map, the `map_get()` function can be used. +To retrieve a value from a map, the `map_get()` macro can be used. `map_get()` will return a pointer to the key's value, or `NULL` if no mapping for that key exists. ```c @@ -31,7 +44,7 @@ if (val) { } ``` -When you are done with a map the `map_deinit()` function should be called on +When you are done with a map the `map_deinit()` macro should be called on it. This will free any memory the map allocated during use. ```c map_deinit(&m); @@ -39,17 +52,6 @@ map_deinit(&m); ## Types -map.h provides the following predefined map types: - -Contained Type | Type name -----------------|---------------------------------- -void* | map_void_t -char* | map_str_t -int | map_int_t -char | map_char_t -float | map_float_t -double | map_double_t - To define a new map type the `map_t()` macro should be used: ```c /* Creates the type uint_map_t for storing unsigned ints */ @@ -68,8 +70,9 @@ Creates a map struct for containing values of type `T`. typedef map_t(FILE*) fp_map_t; ``` -### map\_init(m) -Initialises the map, this must be called before the map can be used. +### map\_init(m, initial_nbuckets) +Initialises the map, this must be called before the map can be used. The parameter +`initial_nbuckets` sets the number of buckets to be pre-allocated. ### map\_deinit(m) Deinitialises the map, freeing the memory the map allocated during use; diff --git a/package.json b/package.json index 7b5115b..b717e47 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "map", - "version": "0.1.0", - "repo": "rxi/map", + "version": "0.1.2", + "repo": "gcerretani/map", "description": "Type-safe generic hash map", "keywords": ["hashmap", "map", "table", "hashtable", "dict", "dictionary"], "license": "MIT", diff --git a/src/map.c b/src/map.c index 308ccad..a13f20c 100644 --- a/src/map.c +++ b/src/map.c @@ -7,187 +7,232 @@ #include #include +#include + #include "map.h" struct map_node_t { - unsigned hash; - void *value; - map_node_t *next; - /* char key[]; */ - /* char value[]; */ + unsigned int hash; + void *value; + char *key; + map_node_t *next; }; -static unsigned map_hash(const char *str) { - unsigned hash = 5381; - while (*str) { - hash = ((hash << 5) + hash) ^ *str++; - } - return hash; +static unsigned int map_hash(const char *str) { + unsigned int hash = 5381; + while (*str) { + hash = ((hash << 5) + hash) ^ *str++; + } + return hash; +} + + +static map_node_t *map_newnode(const char *key, const void *value, int vsize) { + map_node_t *node = malloc(sizeof(*node)); + if (!node) + return NULL; + node->hash = map_hash(key); + node->key = strdup(key); + if (node->key == NULL) { + free(node); + return NULL; + } + node->value = malloc(vsize); + if (node->value == NULL) { + free(node->key); + free(node); + return NULL; + } + memcpy(node->value, value, vsize); + node->next = NULL; + return node; } -static map_node_t *map_newnode(const char *key, void *value, int vsize) { - map_node_t *node; - int ksize = strlen(key) + 1; - int voffset = ksize + ((sizeof(void*) - ksize) % sizeof(void*)); - node = malloc(sizeof(*node) + voffset + vsize); - if (!node) return NULL; - memcpy(node + 1, key, ksize); - node->hash = map_hash(key); - node->value = ((char*) (node + 1)) + voffset; - memcpy(node->value, value, vsize); - return node; +static void map_deletenode(map_node_t *node) { + if (node == NULL) + return; + free(node->value); + free(node->key); + free(node); } -static int map_bucketidx(map_base_t *m, unsigned hash) { - /* If the implementation is changed to allow a non-power-of-2 bucket count, - * the line below should be changed to use mod instead of AND */ - return hash & (m->nbuckets - 1); +static int map_bucketidx(map_base_t *m, unsigned int hash) { + /* If the implementation is changed to allow a non-power-of-2 bucket count, + * the line below should be changed to use mod instead of AND */ + return hash & (m->nbuckets - 1); } static void map_addnode(map_base_t *m, map_node_t *node) { - int n = map_bucketidx(m, node->hash); - node->next = m->buckets[n]; - m->buckets[n] = node; + int n = map_bucketidx(m, node->hash); + node->next = m->buckets[n]; + m->buckets[n] = node; } -static int map_resize(map_base_t *m, int nbuckets) { - map_node_t *nodes, *node, *next; - map_node_t **buckets; - int i; - /* Chain all nodes together */ - nodes = NULL; - i = m->nbuckets; - while (i--) { - node = (m->buckets)[i]; - while (node) { - next = node->next; - node->next = nodes; - nodes = node; - node = next; - } - } - /* Reset buckets */ - buckets = realloc(m->buckets, sizeof(*m->buckets) * nbuckets); - if (buckets != NULL) { - m->buckets = buckets; - m->nbuckets = nbuckets; - } - if (m->buckets) { - memset(m->buckets, 0, sizeof(*m->buckets) * m->nbuckets); - /* Re-add nodes to buckets */ - node = nodes; - while (node) { - next = node->next; - map_addnode(m, node); - node = next; - } - } - /* Return error code if realloc() failed */ - return (buckets == NULL) ? -1 : 0; +static int map_resize(map_base_t *m, unsigned int nbuckets) { + map_node_t *nodes, *node, *next; + map_node_t **buckets; + /* Chain all nodes together */ + nodes = NULL; + unsigned int i = m->nbuckets; + while (i--) { + node = (m->buckets)[i]; + while (node) { + next = node->next; + node->next = nodes; + nodes = node; + node = next; + } + } + /* Reset buckets */ + buckets = realloc(m->buckets, sizeof(*m->buckets) * nbuckets); + if (buckets != NULL) { + m->buckets = buckets; + m->nbuckets = nbuckets; + } + if (m->buckets) { + memset(m->buckets, 0, sizeof(*m->buckets) * m->nbuckets); + /* Re-add nodes to buckets */ + node = nodes; + while (node) { + next = node->next; + map_addnode(m, node); + node = next; + } + } + /* Return error code if realloc() failed */ + return (buckets == NULL) ? -1 : 0; } static map_node_t **map_getref(map_base_t *m, const char *key) { - unsigned hash = map_hash(key); - map_node_t **next; - if (m->nbuckets > 0) { - next = &m->buckets[map_bucketidx(m, hash)]; - while (*next) { - if ((*next)->hash == hash && !strcmp((char*) (*next + 1), key)) { - return next; - } - next = &(*next)->next; - } - } - return NULL; + unsigned int hash = map_hash(key); + if (m->nbuckets > 0) { + map_node_t **next = &m->buckets[map_bucketidx(m, hash)]; + while (*next) { + if ((*next)->hash == hash && !strcmp((*next)->key, key)) { + return next; + } + next = &(*next)->next; + } + } + return NULL; +} + + +int map_init_(map_base_t *m, unsigned int initial_nbuckets) { + // Clear the memory. + memset(m, 0, sizeof(*m)); + m->initialized = true; + // Pre-initialize buckets array only if initial_nbuckets is a power of 2. + // Anyway, it is reallocated automatically when needed. + if ((initial_nbuckets > 0) && !(initial_nbuckets & (initial_nbuckets - 1))) + return map_resize(m, initial_nbuckets); + else + return 0; } void map_deinit_(map_base_t *m) { - map_node_t *next, *node; - int i; - i = m->nbuckets; - while (i--) { - node = m->buckets[i]; - while (node) { - next = node->next; - free(node); - node = next; - } - } - free(m->buckets); + if (m == NULL) + return; + if (!m->initialized) + return; + unsigned int i = m->nbuckets; + while (i--) { + map_node_t *node = m->buckets[i]; + while (node) { + map_node_t *next = node->next; + map_deletenode(node); + node = next; + } + } + free(m->buckets); + m->initialized = false; } void *map_get_(map_base_t *m, const char *key) { - map_node_t **next = map_getref(m, key); - return next ? (*next)->value : NULL; + if (m == NULL || key == NULL) + return NULL; + if (!m->initialized) + return NULL; + map_node_t **next = map_getref(m, key); + return next ? (*next)->value : NULL; } -int map_set_(map_base_t *m, const char *key, void *value, int vsize) { - int n, err; - map_node_t **next, *node; - /* Find & replace existing node */ - next = map_getref(m, key); - if (next) { - memcpy((*next)->value, value, vsize); - return 0; - } - /* Add new node */ - node = map_newnode(key, value, vsize); - if (node == NULL) goto fail; - if (m->nnodes >= m->nbuckets) { - n = (m->nbuckets > 0) ? (m->nbuckets << 1) : 1; - err = map_resize(m, n); - if (err) goto fail; - } - map_addnode(m, node); - m->nnodes++; - return 0; - fail: - if (node) free(node); - return -1; +int map_set_(map_base_t *m, const char *key, const void *value, int vsize) { + if (m == NULL || key == NULL || value == NULL) + return -1; + if (!m->initialized) + return -1; + /* Find & replace existing node */ + map_node_t **next = map_getref(m, key); + if (next) { + memcpy((*next)->value, value, vsize); + return 0; + } + /* Add new node */ + map_node_t *node = map_newnode(key, value, vsize); + if (node == NULL) goto fail; + if (m->nnodes >= m->nbuckets) { + unsigned int n = (m->nbuckets > 0) ? (m->nbuckets << 1) : 1; + int err = map_resize(m, n); + if (err) goto fail; + } + map_addnode(m, node); + m->nnodes++; + return 0; + fail: + map_deletenode(node); + return -1; } void map_remove_(map_base_t *m, const char *key) { - map_node_t *node; - map_node_t **next = map_getref(m, key); - if (next) { - node = *next; - *next = (*next)->next; - free(node); - m->nnodes--; - } + if (m == NULL || key == NULL) + return; + if (!m->initialized) + return; + map_node_t **next = map_getref(m, key); + if (next) { + map_node_t *node = *next; + *next = (*next)->next; + map_deletenode(node); + m->nnodes--; + } } map_iter_t map_iter_(void) { - map_iter_t iter; - iter.bucketidx = -1; - iter.node = NULL; - return iter; + map_iter_t iter; + iter.bucketidx = -1; + iter.node = NULL; + return iter; } const char *map_next_(map_base_t *m, map_iter_t *iter) { - if (iter->node) { - iter->node = iter->node->next; - if (iter->node == NULL) goto nextBucket; - } else { - nextBucket: - do { - if (++iter->bucketidx >= m->nbuckets) { - return NULL; - } - iter->node = m->buckets[iter->bucketidx]; - } while (iter->node == NULL); - } - return (char*) (iter->node + 1); + if (m == NULL || iter == NULL) + return NULL; + if (!m->initialized) + return NULL; + if (iter->node) { + iter->node = iter->node->next; + if (iter->node == NULL) goto nextBucket; + } else { + nextBucket: + do { + if (++iter->bucketidx >= m->nbuckets) { + return NULL; + } + iter->node = m->buckets[iter->bucketidx]; + } while (iter->node == NULL); + } + return iter->node->key; } diff --git a/src/map.h b/src/map.h index 71af710..945d2ba 100644 --- a/src/map.h +++ b/src/map.h @@ -9,69 +9,60 @@ #define MAP_H #include +#include -#define MAP_VERSION "0.1.0" +#define MAP_VERSION "0.1.2" struct map_node_t; typedef struct map_node_t map_node_t; typedef struct { - map_node_t **buckets; - unsigned nbuckets, nnodes; + map_node_t **buckets; + unsigned int nbuckets, nnodes; + bool initialized; } map_base_t; typedef struct { - unsigned bucketidx; - map_node_t *node; + unsigned int bucketidx; + map_node_t *node; } map_iter_t; #define map_t(T)\ - struct { map_base_t base; T *ref; T tmp; } + struct { map_base_t base; T *ref; T tmp; } +#define map_init_reserve(m, initial_nbuckets)\ + ( ((m) != NULL) ? map_init_(&(m)->base, initial_nbuckets) : -1 ) #define map_init(m)\ - memset(m, 0, sizeof(*(m))) - + map_init_reserve(m, 0) #define map_deinit(m)\ - map_deinit_(&(m)->base) - + do { if ((m) != NULL) { map_deinit_(&(m)->base); } } while (0) #define map_get(m, key)\ - ( (m)->ref = map_get_(&(m)->base, key) ) - + ( ((m) != NULL) ? ((m)->ref = map_get_(&(m)->base, key)) : NULL ) #define map_set(m, key, value)\ - ( (m)->tmp = (value),\ - map_set_(&(m)->base, key, &(m)->tmp, sizeof((m)->tmp)) ) - + ( ((m) != NULL) ? ((m)->tmp = (value), map_set_(&(m)->base, key, &(m)->tmp, sizeof((m)->tmp))) : -1 ) #define map_remove(m, key)\ - map_remove_(&(m)->base, key) - + do { if ((m) != NULL) { map_remove_(&(m)->base, key); } } while (0) #define map_iter(m)\ - map_iter_() - + map_iter_() #define map_next(m, iter)\ - map_next_(&(m)->base, iter) + ( ((m) != NULL) ? map_next_(&(m)->base, iter) : NULL ) +int map_init_(map_base_t *m, unsigned int initial_nbuckets); void map_deinit_(map_base_t *m); void *map_get_(map_base_t *m, const char *key); -int map_set_(map_base_t *m, const char *key, void *value, int vsize); +int map_set_(map_base_t *m, const char *key, const void *value, int vsize); void map_remove_(map_base_t *m, const char *key); map_iter_t map_iter_(void); const char *map_next_(map_base_t *m, map_iter_t *iter); -typedef map_t(void*) map_void_t; -typedef map_t(char*) map_str_t; -typedef map_t(int) map_int_t; -typedef map_t(char) map_char_t; -typedef map_t(float) map_float_t; -typedef map_t(double) map_double_t; - #endif diff --git a/src/test.c b/src/test.c new file mode 100644 index 0000000..efe9a99 --- /dev/null +++ b/src/test.c @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2019-2022 Giovanni Cerretani + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#include "map.h" +#include +#include + +typedef map_t(int) map_int_t; + +int main() { + int ret = 0; + + map_int_t *map = malloc(sizeof(*map)); + if (map == NULL) { + ret = 1; + goto quit; + } + + const int value = 12; + const char key[] = "mykey"; + + map_init_reserve(map, 16); + map_set(map, key, value); + int *value_get = map_get(map, key); + + if (value_get == NULL) { + ret = 2; + goto quit; + } + + if (*value_get != value) { + ret = 3; + goto quit; + } + +quit: + map_deinit(map); + free(map); + printf("%d\n", ret); + + return ret; +}