From bb464d8226c24e3713aac2fafc4529bb738ccd42 Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Fri, 25 Feb 2022 20:50:43 +0100 Subject: [PATCH] Hashmap: Implement prepared setting With a prepared set, the potential allocation, which can fail, is split from the actual setting, which can not fail. --- src/hashmap.c | 83 +++++++++++++++++++++++++++++++++++++++------------ src/hashmap.h | 20 +++++++++++++ 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/src/hashmap.c b/src/hashmap.c index a34908d..ace01ec 100644 --- a/src/hashmap.c +++ b/src/hashmap.c @@ -139,24 +139,54 @@ find_key_in_bucket(const struct apfl_hashmap map, struct apfl_hashmap_bucket *bu return false; } -static bool -set_in_bucket(struct apfl_hashmap map, struct apfl_hashmap_bucket *bucket, void *key, void *value) +void +apfl_hashmap_set_prepared(struct apfl_hashmap_prepared_set prep, void *value) { - size_t keysize = map.keysize; - size_t valsize = map.valsize; + size_t keysize = prep.map->keysize; + size_t valsize = prep.map->valsize; + + if (prep.i < prep.bucket->len) { + // Replace + void *dest = KVADDR(prep.bucket->values, valsize, prep.i); + destroy_value(*prep.map, dest); + + copy_value(*prep.map, dest, value); + } else { + // Append + assert(prep.i == prep.bucket->len); + assert(prep.i <= prep.bucket->keys_cap); + assert(prep.i <= prep.bucket->values_cap); + copy_key(*prep.map, KVADDR(prep.bucket->keys, keysize, prep.i), prep.key); + copy_value(*prep.map, KVADDR(prep.bucket->values, valsize, prep.i), value); + + prep.bucket->len++; + } +} + +static bool +prepare_set_in_bucket( + struct apfl_hashmap *map, + struct apfl_hashmap_prepared_set *prep, + struct apfl_hashmap_bucket *bucket, + void *key +) { + size_t keysize = map->keysize; + size_t valsize = map->valsize; size_t i; - if (find_key_in_bucket(map, bucket, key, &i)) { - void *dest = KVADDR(bucket->values, valsize, i); - destroy_value(map, dest); - - copy_value(map, dest, value); + if (find_key_in_bucket(*map, bucket, key, &i)) { + *prep = (struct apfl_hashmap_prepared_set) { + .map = map, + .bucket = bucket, + .key = key, + .i = i, + }; return true; } if (bucket->keys_cap != bucket->values_cap) { - assert(false); // A set operation was called on the hashmap, - // after a previous set operation failed. + assert(false); // A prepare set operation was called on the hashmap, + // after a previous prepare set operation failed. return false; } @@ -166,7 +196,7 @@ set_in_bucket(struct apfl_hashmap map, struct apfl_hashmap_bucket *bucket, void void *newmem; newmem = REALLOC_BYTES( - map.allocator, + map->allocator, bucket->keys, bucket->keys_cap * keysize, new_cap * keysize @@ -178,7 +208,7 @@ set_in_bucket(struct apfl_hashmap map, struct apfl_hashmap_bucket *bucket, void bucket->keys_cap = new_cap; newmem = REALLOC_BYTES( - map.allocator, + map->allocator, bucket->values, bucket->values_cap * valsize, new_cap * valsize @@ -190,11 +220,12 @@ set_in_bucket(struct apfl_hashmap map, struct apfl_hashmap_bucket *bucket, void bucket->values_cap = new_cap; } - copy_key(map, KVADDR(bucket->keys, keysize, bucket->len), key); - copy_value(map, KVADDR(bucket->values, valsize, bucket->len), value); - - bucket->len++; - + *prep = (struct apfl_hashmap_prepared_set) { + .map = map, + .bucket = bucket, + .key = key, + .i = bucket->len, + }; return true; } @@ -332,7 +363,21 @@ apfl_hashmap_get(const struct apfl_hashmap *map, const void *key, void *value) bool apfl_hashmap_set(struct apfl_hashmap *map, void *key, void *value) { - return set_in_bucket(*map, bucket_by_key(*map, key), key, value); + struct apfl_hashmap_prepared_set prep; + if (!apfl_hashmap_prepare_set(map, &prep, key)) { + return false; + } + apfl_hashmap_set_prepared(prep, value); + return true; +} + +bool +apfl_hashmap_prepare_set( + struct apfl_hashmap *map, + struct apfl_hashmap_prepared_set *prep, + void *key +) { + return prepare_set_in_bucket(map, prep, bucket_by_key(*map, key), key); } static void diff --git a/src/hashmap.h b/src/hashmap.h index 444542e..6b73f0b 100644 --- a/src/hashmap.h +++ b/src/hashmap.h @@ -61,6 +61,13 @@ struct apfl_hashmap { struct apfl_hashmap_bucket *buckets; }; +struct apfl_hashmap_prepared_set { + struct apfl_hashmap *map; + struct apfl_hashmap_bucket *bucket; + void *key; + size_t i; +}; + apfl_hash apfl_hash_fnv1a_add(const void *, size_t len, apfl_hash); apfl_hash apfl_hash_fnv1a(const void *, size_t len); @@ -78,6 +85,12 @@ bool apfl_hashmap_set(struct apfl_hashmap *, void *key, void *value); size_t apfl_hashmap_count(const struct apfl_hashmap); void apfl_hashmap_deinit(struct apfl_hashmap *); +// Prepares an apfl_hashmap_set operation. If there are no other set operations +// between the prepare and the set, this makes sure the set will always succeed. +bool apfl_hashmap_prepare_set(struct apfl_hashmap *, struct apfl_hashmap_prepared_set *, void *key); + +void apfl_hashmap_set_prepared(struct apfl_hashmap_prepared_set, void *value); + struct apfl_hashmap apfl_hashmap_move(struct apfl_hashmap *src); bool apfl_hashmap_copy(struct apfl_hashmap *dst, struct apfl_hashmap src); @@ -91,6 +104,13 @@ void apfl_hashmap_cursor_peek_value(struct apfl_hashmap_cursor, void **value_ptr void apfl_hashmap_cursor_get_key(struct apfl_hashmap_cursor, void *key); void apfl_hashmap_cursor_get_value(struct apfl_hashmap_cursor, void *value); +#define HASHMAP_EACH(map_ptr,cur) \ + for ( \ + struct apfl_hashmap_cursor cur = apfl_hashmap_get_cursor(map_ptr); \ + !apfl_hashmap_cursor_is_end(cur); \ + apfl_hashmap_cursor_next(&cur) \ + ) + #ifdef __cplusplus } #endif