From 6474b869393b2d40b6e1b3ab5633ce2bad6abe48 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Tue, 13 Oct 2020 00:40:41 +0000 Subject: [PATCH 01/15] hashmap: add usage documentation explaining hashmap_free[_entries]() The existence of hashmap_free() and hashmap_free_entries() confused me, and the docs weren't clear enough. We are dealing with a map table, entries in that table, and possibly also things each of those entries point to. I had to consult other source code examples and the implementation. Add a brief note to clarify the differences. This will become even more important once we introduce a new hashmap_partial_clear() function which will add the question of whether the table itself has been freed. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- hashmap.h | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/hashmap.h b/hashmap.h index b011b394fe..2994dc7a9c 100644 --- a/hashmap.h +++ b/hashmap.h @@ -236,13 +236,40 @@ void hashmap_init(struct hashmap *map, void hashmap_free_(struct hashmap *map, ssize_t offset); /* - * Frees a hashmap structure and allocated memory, leaves entries undisturbed + * Frees a hashmap structure and allocated memory for the table, but does not + * free the entries nor anything they point to. + * + * Usage note: + * + * Many callers will need to iterate over all entries and free the data each + * entry points to; in such a case, they can free the entry itself while at it. + * Thus, you might see: + * + * hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) { + * free(e->somefield); + * free(e); + * } + * hashmap_free(map); + * + * instead of + * + * hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) { + * free(e->somefield); + * } + * hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name); + * + * to avoid the implicit extra loop over the entries. However, if there are + * no special fields in your entry that need to be freed beyond the entry + * itself, it is probably simpler to avoid the explicit loop and just call + * hashmap_free_entries(). */ #define hashmap_free(map) hashmap_free_(map, -1) /* * Frees @map and all entries. @type is the struct type of the entry - * where @member is the hashmap_entry struct used to associate with @map + * where @member is the hashmap_entry struct used to associate with @map. + * + * See usage note above hashmap_free(). */ #define hashmap_free_entries(map, type, member) \ hashmap_free_(map, offsetof(type, member)); From 97a39a4a930ebec9162f90ebd0412aed47d413d0 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Mon, 2 Nov 2020 18:55:02 +0000 Subject: [PATCH 02/15] hashmap: adjust spacing to fix argument alignment No actual code changes; just whitespace adjustments. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- hashmap.c | 17 +++++++++-------- hashmap.h | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/hashmap.c b/hashmap.c index 09813e1a46..e44d8a3e85 100644 --- a/hashmap.c +++ b/hashmap.c @@ -92,8 +92,9 @@ static void alloc_table(struct hashmap *map, unsigned int size) } static inline int entry_equals(const struct hashmap *map, - const struct hashmap_entry *e1, const struct hashmap_entry *e2, - const void *keydata) + const struct hashmap_entry *e1, + const struct hashmap_entry *e2, + const void *keydata) { return (e1 == e2) || (e1->hash == e2->hash && @@ -101,7 +102,7 @@ static inline int entry_equals(const struct hashmap *map, } static inline unsigned int bucket(const struct hashmap *map, - const struct hashmap_entry *key) + const struct hashmap_entry *key) { return key->hash & (map->tablesize - 1); } @@ -148,7 +149,7 @@ static int always_equal(const void *unused_cmp_data, } void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function, - const void *cmpfn_data, size_t initial_size) + const void *cmpfn_data, size_t initial_size) { unsigned int size = HASHMAP_INITIAL_SIZE; @@ -199,7 +200,7 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map, } struct hashmap_entry *hashmap_get_next(const struct hashmap *map, - const struct hashmap_entry *entry) + const struct hashmap_entry *entry) { struct hashmap_entry *e = entry->next; for (; e; e = e->next) @@ -225,8 +226,8 @@ void hashmap_add(struct hashmap *map, struct hashmap_entry *entry) } struct hashmap_entry *hashmap_remove(struct hashmap *map, - const struct hashmap_entry *key, - const void *keydata) + const struct hashmap_entry *key, + const void *keydata) { struct hashmap_entry *old; struct hashmap_entry **e = find_entry_ptr(map, key, keydata); @@ -249,7 +250,7 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map, } struct hashmap_entry *hashmap_put(struct hashmap *map, - struct hashmap_entry *entry) + struct hashmap_entry *entry) { struct hashmap_entry *old = hashmap_remove(map, entry, NULL); hashmap_add(map, entry); diff --git a/hashmap.h b/hashmap.h index 2994dc7a9c..904f61d6e1 100644 --- a/hashmap.h +++ b/hashmap.h @@ -228,9 +228,9 @@ struct hashmap { * prevent expensive resizing. If 0, the table is dynamically resized. */ void hashmap_init(struct hashmap *map, - hashmap_cmp_fn equals_function, - const void *equals_function_data, - size_t initial_size); + hashmap_cmp_fn equals_function, + const void *equals_function_data, + size_t initial_size); /* internal function for freeing hashmap */ void hashmap_free_(struct hashmap *map, ssize_t offset); @@ -288,7 +288,7 @@ void hashmap_free_(struct hashmap *map, ssize_t offset); * and if it is on stack, you can just let it go out of scope). */ static inline void hashmap_entry_init(struct hashmap_entry *e, - unsigned int hash) + unsigned int hash) { e->hash = hash; e->next = NULL; @@ -330,8 +330,8 @@ static inline unsigned int hashmap_get_size(struct hashmap *map) * to `hashmap_cmp_fn` to decide whether the entry matches the key. */ struct hashmap_entry *hashmap_get(const struct hashmap *map, - const struct hashmap_entry *key, - const void *keydata); + const struct hashmap_entry *key, + const void *keydata); /* * Returns the hashmap entry for the specified hash code and key data, @@ -364,7 +364,7 @@ static inline struct hashmap_entry *hashmap_get_from_hash( * call to `hashmap_get` or `hashmap_get_next`. */ struct hashmap_entry *hashmap_get_next(const struct hashmap *map, - const struct hashmap_entry *entry); + const struct hashmap_entry *entry); /* * Adds a hashmap entry. This allows to add duplicate entries (i.e. @@ -384,7 +384,7 @@ void hashmap_add(struct hashmap *map, struct hashmap_entry *entry); * Returns the replaced entry, or NULL if not found (i.e. the entry was added). */ struct hashmap_entry *hashmap_put(struct hashmap *map, - struct hashmap_entry *entry); + struct hashmap_entry *entry); /* * Adds or replaces a hashmap entry contained within @keyvar, @@ -406,8 +406,8 @@ struct hashmap_entry *hashmap_put(struct hashmap *map, * Argument explanation is the same as in `hashmap_get`. */ struct hashmap_entry *hashmap_remove(struct hashmap *map, - const struct hashmap_entry *key, - const void *keydata); + const struct hashmap_entry *key, + const void *keydata); /* * Removes a hashmap entry contained within @keyvar, @@ -449,7 +449,7 @@ struct hashmap_entry *hashmap_iter_next(struct hashmap_iter *iter); /* Initializes the iterator and returns the first entry, if any. */ static inline struct hashmap_entry *hashmap_iter_first(struct hashmap *map, - struct hashmap_iter *iter) + struct hashmap_iter *iter) { hashmap_iter_init(map, iter); return hashmap_iter_next(iter); From b7879b0ba6ee1306a42227f7fd7f4e5f50409184 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Mon, 2 Nov 2020 18:55:03 +0000 Subject: [PATCH 03/15] hashmap: allow re-use after hashmap_free() Previously, once map->table had been freed, any calls to hashmap_put(), hashmap_get(), or hashmap_remove() would cause a NULL pointer dereference (since hashmap_free_() also zeros the memory; without that zeroing, calling these functions would cause a use-after-free problem). Modify these functions to check for a NULL table and automatically allocate as needed. Also add a HASHMAP_INIT(fn, data) macro for initializing hashmaps on the stack without calling hashmap_init(). Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- hashmap.c | 16 ++++++++++++++-- hashmap.h | 3 +++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/hashmap.c b/hashmap.c index e44d8a3e85..bb7c9979b8 100644 --- a/hashmap.c +++ b/hashmap.c @@ -114,6 +114,7 @@ int hashmap_bucket(const struct hashmap *map, unsigned int hash) static void rehash(struct hashmap *map, unsigned int newsize) { + /* map->table MUST NOT be NULL when this function is called */ unsigned int i, oldsize = map->tablesize; struct hashmap_entry **oldtable = map->table; @@ -134,6 +135,7 @@ static void rehash(struct hashmap *map, unsigned int newsize) static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map, const struct hashmap_entry *key, const void *keydata) { + /* map->table MUST NOT be NULL when this function is called */ struct hashmap_entry **e = &map->table[bucket(map, key)]; while (*e && !entry_equals(map, *e, key, keydata)) e = &(*e)->next; @@ -196,6 +198,8 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map, const struct hashmap_entry *key, const void *keydata) { + if (!map->table) + return NULL; return *find_entry_ptr(map, key, keydata); } @@ -211,8 +215,12 @@ struct hashmap_entry *hashmap_get_next(const struct hashmap *map, void hashmap_add(struct hashmap *map, struct hashmap_entry *entry) { - unsigned int b = bucket(map, entry); + unsigned int b; + if (!map->table) + alloc_table(map, HASHMAP_INITIAL_SIZE); + + b = bucket(map, entry); /* add entry */ entry->next = map->table[b]; map->table[b] = entry; @@ -230,7 +238,11 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map, const void *keydata) { struct hashmap_entry *old; - struct hashmap_entry **e = find_entry_ptr(map, key, keydata); + struct hashmap_entry **e; + + if (!map->table) + return NULL; + e = find_entry_ptr(map, key, keydata); if (!*e) return NULL; diff --git a/hashmap.h b/hashmap.h index 904f61d6e1..3b0f2bcade 100644 --- a/hashmap.h +++ b/hashmap.h @@ -210,6 +210,9 @@ struct hashmap { /* hashmap functions */ +#define HASHMAP_INIT(fn, data) { .cmpfn = fn, .cmpfn_data = data, \ + .do_count_items = 1 } + /* * Initializes a hashmap structure. * From 33f20d82177871225e17d9dd44169a52a36c9f1d Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Mon, 2 Nov 2020 18:55:04 +0000 Subject: [PATCH 04/15] hashmap: introduce a new hashmap_partial_clear() merge-ort is a heavy user of strmaps, which are built on hashmap.[ch]. clear_or_reinit_internal_opts() in merge-ort was taking about 12% of overall runtime in my testcase involving rebasing 35 patches of linux.git across a big rename. clear_or_reinit_internal_opts() was calling hashmap_free() followed by hashmap_init(), meaning that not only was it freeing all the memory associated with each of the strmaps just to immediately allocate a new array again, it was allocating a new array that was likely smaller than needed (thus resulting in later need to rehash things). The ending size of the map table on the previous commit was likely almost perfectly sized for the next commit we wanted to pick, and not dropping and reallocating the table immediately is a win. Add some new API to hashmap to clear a hashmap of entries without freeing map->table (and instead only zeroing it out like alloc_table() would do, along with zeroing the count of items in the table and the shrink_at field). Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- hashmap.c | 39 +++++++++++++++++++++++++++------------ hashmap.h | 13 ++++++++++++- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/hashmap.c b/hashmap.c index bb7c9979b8..922ed07954 100644 --- a/hashmap.c +++ b/hashmap.c @@ -174,22 +174,37 @@ void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function, map->do_count_items = 1; } +static void free_individual_entries(struct hashmap *map, ssize_t entry_offset) +{ + struct hashmap_iter iter; + struct hashmap_entry *e; + + hashmap_iter_init(map, &iter); + while ((e = hashmap_iter_next(&iter))) + /* + * like container_of, but using caller-calculated + * offset (caller being hashmap_free_entries) + */ + free((char *)e - entry_offset); +} + +void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset) +{ + if (!map || !map->table) + return; + if (entry_offset >= 0) /* called by hashmap_clear_entries */ + free_individual_entries(map, entry_offset); + memset(map->table, 0, map->tablesize * sizeof(struct hashmap_entry *)); + map->shrink_at = 0; + map->private_size = 0; +} + void hashmap_free_(struct hashmap *map, ssize_t entry_offset) { if (!map || !map->table) return; - if (entry_offset >= 0) { /* called by hashmap_free_entries */ - struct hashmap_iter iter; - struct hashmap_entry *e; - - hashmap_iter_init(map, &iter); - while ((e = hashmap_iter_next(&iter))) - /* - * like container_of, but using caller-calculated - * offset (caller being hashmap_free_entries) - */ - free((char *)e - entry_offset); - } + if (entry_offset >= 0) /* called by hashmap_free_entries */ + free_individual_entries(map, entry_offset); free(map->table); memset(map, 0, sizeof(*map)); } diff --git a/hashmap.h b/hashmap.h index 3b0f2bcade..e9430d582a 100644 --- a/hashmap.h +++ b/hashmap.h @@ -235,7 +235,8 @@ void hashmap_init(struct hashmap *map, const void *equals_function_data, size_t initial_size); -/* internal function for freeing hashmap */ +/* internal functions for clearing or freeing hashmap */ +void hashmap_partial_clear_(struct hashmap *map, ssize_t offset); void hashmap_free_(struct hashmap *map, ssize_t offset); /* @@ -268,6 +269,16 @@ void hashmap_free_(struct hashmap *map, ssize_t offset); */ #define hashmap_free(map) hashmap_free_(map, -1) +/* + * Basically the same as calling hashmap_free() followed by hashmap_init(), + * but doesn't incur the overhead of deallocating and reallocating + * map->table; it leaves map->table allocated and the same size but zeroes + * it out so it's ready for use again as an empty map. As with + * hashmap_free(), you may need to free the entries yourself before calling + * this function. + */ +#define hashmap_partial_clear(map) hashmap_partial_clear_(map, -1) + /* * Frees @map and all entries. @type is the struct type of the entry * where @member is the hashmap_entry struct used to associate with @map. From 6da1a258142ac2422c8c57c54b92eaed3c86226e Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Mon, 2 Nov 2020 18:55:05 +0000 Subject: [PATCH 05/15] hashmap: provide deallocation function names hashmap_free(), hashmap_free_entries(), and hashmap_free_() have existed for a while, but aren't necessarily the clearest names, especially with hashmap_partial_clear() being added to the mix and lazy-initialization now being supported. Peff suggested we adopt the following names[1]: - hashmap_clear() - remove all entries and de-allocate any hashmap-specific data, but be ready for reuse - hashmap_clear_and_free() - ditto, but free the entries themselves - hashmap_partial_clear() - remove all entries but don't deallocate table - hashmap_partial_clear_and_free() - ditto, but free the entries This patch provides the new names and converts all existing callers over to the new naming scheme. [1] https://lore.kernel.org/git/20201030125059.GA3277724@coredump.intra.peff.net/ Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- add-interactive.c | 2 +- blame.c | 2 +- bloom.c | 2 +- builtin/fetch.c | 6 +++--- builtin/shortlog.c | 2 +- config.c | 2 +- diff.c | 4 ++-- diffcore-rename.c | 2 +- dir.c | 8 ++++---- hashmap.c | 6 +++--- hashmap.h | 44 +++++++++++++++++++++++++---------------- merge-recursive.c | 6 +++--- name-hash.c | 4 ++-- object.c | 2 +- oidmap.c | 2 +- patch-ids.c | 2 +- range-diff.c | 2 +- ref-filter.c | 2 +- revision.c | 2 +- sequencer.c | 4 ++-- submodule-config.c | 4 ++-- t/helper/test-hashmap.c | 6 +++--- 22 files changed, 63 insertions(+), 53 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index 555c4abf32..a14c0feaa2 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -557,7 +557,7 @@ static int get_modified_files(struct repository *r, if (ps) clear_pathspec(&rev.prune_data); } - hashmap_free_entries(&s.file_map, struct pathname_entry, ent); + hashmap_clear_and_free(&s.file_map, struct pathname_entry, ent); if (unmerged_count) *unmerged_count = s.unmerged_count; if (binary_count) diff --git a/blame.c b/blame.c index 686845b2b4..229beb6452 100644 --- a/blame.c +++ b/blame.c @@ -435,7 +435,7 @@ static void get_fingerprint(struct fingerprint *result, static void free_fingerprint(struct fingerprint *f) { - hashmap_free(&f->map); + hashmap_clear(&f->map); free(f->entries); } diff --git a/bloom.c b/bloom.c index 68c73200a5..719c313a1c 100644 --- a/bloom.c +++ b/bloom.c @@ -287,7 +287,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, } cleanup: - hashmap_free_entries(&pathmap, struct pathmap_hash_entry, entry); + hashmap_clear_and_free(&pathmap, struct pathmap_hash_entry, entry); } else { for (i = 0; i < diff_queued_diff.nr; i++) diff_free_filepair(diff_queued_diff.queue[i]); diff --git a/builtin/fetch.c b/builtin/fetch.c index f9c3c49f14..ecf8537605 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -393,7 +393,7 @@ static void find_non_local_tags(const struct ref *refs, item = refname_hash_add(&remote_refs, ref->name, &ref->old_oid); string_list_insert(&remote_refs_list, ref->name); } - hashmap_free_entries(&existing_refs, struct refname_hash_entry, ent); + hashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent); /* * We may have a final lightweight tag that needs to be @@ -428,7 +428,7 @@ static void find_non_local_tags(const struct ref *refs, **tail = rm; *tail = &rm->next; } - hashmap_free_entries(&remote_refs, struct refname_hash_entry, ent); + hashmap_clear_and_free(&remote_refs, struct refname_hash_entry, ent); string_list_clear(&remote_refs_list, 0); oidset_clear(&fetch_oids); } @@ -573,7 +573,7 @@ static struct ref *get_ref_map(struct remote *remote, } } if (existing_refs_populated) - hashmap_free_entries(&existing_refs, struct refname_hash_entry, ent); + hashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent); return ref_map; } diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 0a5c4968f6..83f0a739b4 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -220,7 +220,7 @@ static void strset_clear(struct strset *ss) { if (!ss->map.table) return; - hashmap_free_entries(&ss->map, struct strset_item, ent); + hashmap_clear_and_free(&ss->map, struct strset_item, ent); } static void insert_records_from_trailers(struct shortlog *log, diff --git a/config.c b/config.c index 2bdff4457b..8f324ed3a6 100644 --- a/config.c +++ b/config.c @@ -1963,7 +1963,7 @@ void git_configset_clear(struct config_set *cs) free(entry->key); string_list_clear(&entry->value_list, 1); } - hashmap_free_entries(&cs->config_hash, struct config_set_element, ent); + hashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent); cs->hash_initialized = 0; free(cs->list.items); cs->list.nr = 0; diff --git a/diff.c b/diff.c index 2bb2f8f57e..8e0e59f5cf 100644 --- a/diff.c +++ b/diff.c @@ -6289,9 +6289,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o) if (o->color_moved == COLOR_MOVED_ZEBRA_DIM) dim_moved_lines(o); - hashmap_free_entries(&add_lines, struct moved_entry, + hashmap_clear_and_free(&add_lines, struct moved_entry, ent); - hashmap_free_entries(&del_lines, struct moved_entry, + hashmap_clear_and_free(&del_lines, struct moved_entry, ent); } diff --git a/diffcore-rename.c b/diffcore-rename.c index 99e63e90f8..d367a6d244 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -407,7 +407,7 @@ static int find_exact_renames(struct diff_options *options) renames += find_identical_files(&file_table, i, options); /* Free the hash data structure and entries */ - hashmap_free_entries(&file_table, struct file_similarity, entry); + hashmap_clear_and_free(&file_table, struct file_similarity, entry); return renames; } diff --git a/dir.c b/dir.c index 78387110e6..161dce121e 100644 --- a/dir.c +++ b/dir.c @@ -817,8 +817,8 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern clear_hashmaps: warning(_("disabling cone pattern matching")); - hashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent); - hashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent); + hashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent); + hashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent); pl->use_cone_patterns = 0; } @@ -921,8 +921,8 @@ void clear_pattern_list(struct pattern_list *pl) free(pl->patterns[i]); free(pl->patterns); free(pl->filebuf); - hashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent); - hashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent); + hashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent); + hashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent); memset(pl, 0, sizeof(*pl)); } diff --git a/hashmap.c b/hashmap.c index 922ed07954..5009471800 100644 --- a/hashmap.c +++ b/hashmap.c @@ -183,7 +183,7 @@ static void free_individual_entries(struct hashmap *map, ssize_t entry_offset) while ((e = hashmap_iter_next(&iter))) /* * like container_of, but using caller-calculated - * offset (caller being hashmap_free_entries) + * offset (caller being hashmap_clear_and_free) */ free((char *)e - entry_offset); } @@ -199,11 +199,11 @@ void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset) map->private_size = 0; } -void hashmap_free_(struct hashmap *map, ssize_t entry_offset) +void hashmap_clear_(struct hashmap *map, ssize_t entry_offset) { if (!map || !map->table) return; - if (entry_offset >= 0) /* called by hashmap_free_entries */ + if (entry_offset >= 0) /* called by hashmap_clear_and_free */ free_individual_entries(map, entry_offset); free(map->table); memset(map, 0, sizeof(*map)); diff --git a/hashmap.h b/hashmap.h index e9430d582a..7251687d73 100644 --- a/hashmap.h +++ b/hashmap.h @@ -96,7 +96,7 @@ * } * * if (!strcmp("end", action)) { - * hashmap_free_entries(&map, struct long2string, ent); + * hashmap_clear_and_free(&map, struct long2string, ent); * break; * } * } @@ -237,7 +237,7 @@ void hashmap_init(struct hashmap *map, /* internal functions for clearing or freeing hashmap */ void hashmap_partial_clear_(struct hashmap *map, ssize_t offset); -void hashmap_free_(struct hashmap *map, ssize_t offset); +void hashmap_clear_(struct hashmap *map, ssize_t offset); /* * Frees a hashmap structure and allocated memory for the table, but does not @@ -253,40 +253,50 @@ void hashmap_free_(struct hashmap *map, ssize_t offset); * free(e->somefield); * free(e); * } - * hashmap_free(map); + * hashmap_clear(map); * * instead of * * hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) { * free(e->somefield); * } - * hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name); + * hashmap_clear_and_free(map, struct my_entry_struct, hashmap_entry_name); * * to avoid the implicit extra loop over the entries. However, if there are * no special fields in your entry that need to be freed beyond the entry * itself, it is probably simpler to avoid the explicit loop and just call - * hashmap_free_entries(). + * hashmap_clear_and_free(). */ -#define hashmap_free(map) hashmap_free_(map, -1) +#define hashmap_clear(map) hashmap_clear_(map, -1) /* - * Basically the same as calling hashmap_free() followed by hashmap_init(), - * but doesn't incur the overhead of deallocating and reallocating - * map->table; it leaves map->table allocated and the same size but zeroes - * it out so it's ready for use again as an empty map. As with - * hashmap_free(), you may need to free the entries yourself before calling - * this function. + * Similar to hashmap_clear(), except that the table is no deallocated; it + * is merely zeroed out but left the same size as before. If the hashmap + * will be reused, this avoids the overhead of deallocating and + * reallocating map->table. As with hashmap_clear(), you may need to free + * the entries yourself before calling this function. */ #define hashmap_partial_clear(map) hashmap_partial_clear_(map, -1) /* - * Frees @map and all entries. @type is the struct type of the entry - * where @member is the hashmap_entry struct used to associate with @map. + * Similar to hashmap_clear() but also frees all entries. @type is the + * struct type of the entry where @member is the hashmap_entry struct used + * to associate with @map. * - * See usage note above hashmap_free(). + * See usage note above hashmap_clear(). */ -#define hashmap_free_entries(map, type, member) \ - hashmap_free_(map, offsetof(type, member)); +#define hashmap_clear_and_free(map, type, member) \ + hashmap_clear_(map, offsetof(type, member)) + +/* + * Similar to hashmap_partial_clear() but also frees all entries. @type is + * the struct type of the entry where @member is the hashmap_entry struct + * used to associate with @map. + * + * See usage note above hashmap_clear(). + */ +#define hashmap_partial_clear_and_free(map, type, member) \ + hashmap_partial_clear_(map, offsetof(type, member)) /* hashmap_entry functions */ diff --git a/merge-recursive.c b/merge-recursive.c index d0214335a7..f736a0f632 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -2651,7 +2651,7 @@ static struct string_list *get_renames(struct merge_options *opt, free(e->target_file); string_list_clear(&e->source_files, 0); } - hashmap_free_entries(&collisions, struct collision_entry, ent); + hashmap_clear_and_free(&collisions, struct collision_entry, ent); return renames; } @@ -2870,7 +2870,7 @@ static void initial_cleanup_rename(struct diff_queue_struct *pairs, strbuf_release(&e->new_dir); /* possible_new_dirs already cleared in get_directory_renames */ } - hashmap_free_entries(dir_renames, struct dir_rename_entry, ent); + hashmap_clear_and_free(dir_renames, struct dir_rename_entry, ent); free(dir_renames); free(pairs->queue); @@ -3497,7 +3497,7 @@ static int merge_trees_internal(struct merge_options *opt, string_list_clear(entries, 1); free(entries); - hashmap_free_entries(&opt->priv->current_file_dir_set, + hashmap_clear_and_free(&opt->priv->current_file_dir_set, struct path_hashmap_entry, e); if (clean < 0) { diff --git a/name-hash.c b/name-hash.c index fb526a3775..5d3c7b12c1 100644 --- a/name-hash.c +++ b/name-hash.c @@ -726,6 +726,6 @@ void free_name_hash(struct index_state *istate) return; istate->name_hash_initialized = 0; - hashmap_free(&istate->name_hash); - hashmap_free_entries(&istate->dir_hash, struct dir_entry, ent); + hashmap_clear(&istate->name_hash); + hashmap_clear_and_free(&istate->dir_hash, struct dir_entry, ent); } diff --git a/object.c b/object.c index 3257518656..b8406409d5 100644 --- a/object.c +++ b/object.c @@ -532,7 +532,7 @@ void raw_object_store_clear(struct raw_object_store *o) close_object_store(o); o->packed_git = NULL; - hashmap_free(&o->pack_map); + hashmap_clear(&o->pack_map); } void parsed_object_pool_clear(struct parsed_object_pool *o) diff --git a/oidmap.c b/oidmap.c index 423aa014a3..286a04a53c 100644 --- a/oidmap.c +++ b/oidmap.c @@ -27,7 +27,7 @@ void oidmap_free(struct oidmap *map, int free_entries) return; /* TODO: make oidmap itself not depend on struct layouts */ - hashmap_free_(&map->map, free_entries ? 0 : -1); + hashmap_clear_(&map->map, free_entries ? 0 : -1); } void *oidmap_get(const struct oidmap *map, const struct object_id *key) diff --git a/patch-ids.c b/patch-ids.c index 12aa6d494b..21973e4933 100644 --- a/patch-ids.c +++ b/patch-ids.c @@ -71,7 +71,7 @@ int init_patch_ids(struct repository *r, struct patch_ids *ids) int free_patch_ids(struct patch_ids *ids) { - hashmap_free_entries(&ids->patches, struct patch_id, ent); + hashmap_clear_and_free(&ids->patches, struct patch_id, ent); return 0; } diff --git a/range-diff.c b/range-diff.c index 24dc435e48..befeecae44 100644 --- a/range-diff.c +++ b/range-diff.c @@ -266,7 +266,7 @@ static void find_exact_matches(struct string_list *a, struct string_list *b) } } - hashmap_free(&map); + hashmap_clear(&map); } static void diffsize_consume(void *data, char *line, unsigned long len) diff --git a/ref-filter.c b/ref-filter.c index c62f6b4822..5e66b8cd76 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2222,7 +2222,7 @@ void ref_array_clear(struct ref_array *array) used_atom_cnt = 0; if (ref_to_worktree_map.worktrees) { - hashmap_free_entries(&(ref_to_worktree_map.map), + hashmap_clear_and_free(&(ref_to_worktree_map.map), struct ref_to_worktree_entry, ent); free_worktrees(ref_to_worktree_map.worktrees); ref_to_worktree_map.worktrees = NULL; diff --git a/revision.c b/revision.c index aa62212040..f27649d45d 100644 --- a/revision.c +++ b/revision.c @@ -139,7 +139,7 @@ static void paths_and_oids_clear(struct hashmap *map) free(entry->path); } - hashmap_free_entries(map, struct path_and_oids_entry, ent); + hashmap_clear_and_free(map, struct path_and_oids_entry, ent); } static void paths_and_oids_insert(struct hashmap *map, diff --git a/sequencer.c b/sequencer.c index 00acb12496..23a09c3e7a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -5058,7 +5058,7 @@ static int make_script_with_merges(struct pretty_print_context *pp, oidmap_free(&commit2todo, 1); oidmap_free(&state.commit2label, 1); - hashmap_free_entries(&state.labels, struct labels_entry, entry); + hashmap_clear_and_free(&state.labels, struct labels_entry, entry); strbuf_release(&state.buf); return 0; @@ -5577,7 +5577,7 @@ int todo_list_rearrange_squash(struct todo_list *todo_list) for (i = 0; i < todo_list->nr; i++) free(subjects[i]); free(subjects); - hashmap_free_entries(&subject2item, struct subject2item_entry, entry); + hashmap_clear_and_free(&subject2item, struct subject2item_entry, entry); clear_commit_todo_item(&commit_todo); diff --git a/submodule-config.c b/submodule-config.c index c569e22aa3..f502505566 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -103,8 +103,8 @@ static void submodule_cache_clear(struct submodule_cache *cache) ent /* member name */) free_one_config(entry); - hashmap_free_entries(&cache->for_path, struct submodule_entry, ent); - hashmap_free_entries(&cache->for_name, struct submodule_entry, ent); + hashmap_clear_and_free(&cache->for_path, struct submodule_entry, ent); + hashmap_clear_and_free(&cache->for_name, struct submodule_entry, ent); cache->initialized = 0; cache->gitmodules_read = 0; } diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c index f38706216f..2475663b49 100644 --- a/t/helper/test-hashmap.c +++ b/t/helper/test-hashmap.c @@ -110,7 +110,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) hashmap_add(&map, &entries[i]->ent); } - hashmap_free(&map); + hashmap_clear(&map); } } else { /* test map lookups */ @@ -130,7 +130,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) } } - hashmap_free(&map); + hashmap_clear(&map); } } @@ -262,6 +262,6 @@ int cmd__hashmap(int argc, const char **argv) } strbuf_release(&line); - hashmap_free_entries(&map, struct test_entry, ent); + hashmap_clear_and_free(&map, struct test_entry, ent); return 0; } From ae20bf1ad98bdc716879a8da99e7f329e3cc2730 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Mon, 2 Nov 2020 18:55:06 +0000 Subject: [PATCH 06/15] strmap: new utility functions Add strmap as a new struct and associated utility functions, specifically for hashmaps that map strings to some value. The API is taken directly from Peff's proposal at https://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/ Note that similar string-list, I have a strdup_strings setting. However, unlike string-list, strmap_init() does not take a parameter for this setting and instead automatically sets it to 1; callers who want to control this detail need to instead call strmap_init_with_options(). (Future patches will add additional parameters to strmap_init_with_options()). Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- Makefile | 1 + strmap.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ strmap.h | 65 +++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 strmap.c create mode 100644 strmap.h diff --git a/Makefile b/Makefile index 95571ee3fc..777a34c01c 100644 --- a/Makefile +++ b/Makefile @@ -1000,6 +1000,7 @@ LIB_OBJS += stable-qsort.o LIB_OBJS += strbuf.o LIB_OBJS += streaming.o LIB_OBJS += string-list.o +LIB_OBJS += strmap.o LIB_OBJS += strvec.o LIB_OBJS += sub-process.o LIB_OBJS += submodule-config.o diff --git a/strmap.c b/strmap.c new file mode 100644 index 0000000000..53f284eb20 --- /dev/null +++ b/strmap.c @@ -0,0 +1,99 @@ +#include "git-compat-util.h" +#include "strmap.h" + +int cmp_strmap_entry(const void *hashmap_cmp_fn_data, + const struct hashmap_entry *entry1, + const struct hashmap_entry *entry2, + const void *keydata) +{ + const struct strmap_entry *e1, *e2; + + e1 = container_of(entry1, const struct strmap_entry, ent); + e2 = container_of(entry2, const struct strmap_entry, ent); + return strcmp(e1->key, e2->key); +} + +static struct strmap_entry *find_strmap_entry(struct strmap *map, + const char *str) +{ + struct strmap_entry entry; + hashmap_entry_init(&entry.ent, strhash(str)); + entry.key = str; + return hashmap_get_entry(&map->map, &entry, ent, NULL); +} + +void strmap_init(struct strmap *map) +{ + strmap_init_with_options(map, 1); +} + +void strmap_init_with_options(struct strmap *map, + int strdup_strings) +{ + hashmap_init(&map->map, cmp_strmap_entry, NULL, 0); + map->strdup_strings = strdup_strings; +} + +static void strmap_free_entries_(struct strmap *map, int free_values) +{ + struct hashmap_iter iter; + struct strmap_entry *e; + + if (!map) + return; + + /* + * We need to iterate over the hashmap entries and free + * e->key and e->value ourselves; hashmap has no API to + * take care of that for us. Since we're already iterating over + * the hashmap, though, might as well free e too and avoid the need + * to make some call into the hashmap API to do that. + */ + hashmap_for_each_entry(&map->map, &iter, e, ent) { + if (free_values) + free(e->value); + if (map->strdup_strings) + free((char*)e->key); + free(e); + } +} + +void strmap_clear(struct strmap *map, int free_values) +{ + strmap_free_entries_(map, free_values); + hashmap_clear(&map->map); +} + +void *strmap_put(struct strmap *map, const char *str, void *data) +{ + struct strmap_entry *entry = find_strmap_entry(map, str); + void *old = NULL; + + if (entry) { + old = entry->value; + entry->value = data; + } else { + const char *key = str; + + entry = xmalloc(sizeof(*entry)); + hashmap_entry_init(&entry->ent, strhash(str)); + + if (map->strdup_strings) + key = xstrdup(str); + entry->key = key; + entry->value = data; + hashmap_add(&map->map, &entry->ent); + } + return old; +} + +void *strmap_get(struct strmap *map, const char *str) +{ + struct strmap_entry *entry = find_strmap_entry(map, str); + return entry ? entry->value : NULL; +} + +int strmap_contains(struct strmap *map, const char *str) +{ + return find_strmap_entry(map, str) != NULL; +} diff --git a/strmap.h b/strmap.h new file mode 100644 index 0000000000..96888c23ad --- /dev/null +++ b/strmap.h @@ -0,0 +1,65 @@ +#ifndef STRMAP_H +#define STRMAP_H + +#include "hashmap.h" + +struct strmap { + struct hashmap map; + unsigned int strdup_strings:1; +}; + +struct strmap_entry { + struct hashmap_entry ent; + const char *key; + void *value; +}; + +int cmp_strmap_entry(const void *hashmap_cmp_fn_data, + const struct hashmap_entry *entry1, + const struct hashmap_entry *entry2, + const void *keydata); + +#define STRMAP_INIT { \ + .map = HASHMAP_INIT(cmp_strmap_entry, NULL), \ + .strdup_strings = 1, \ + } + +/* + * Initialize the members of the strmap. Any keys added to the strmap will + * be strdup'ed with their memory managed by the strmap. + */ +void strmap_init(struct strmap *map); + +/* + * Same as strmap_init, but for those who want to control the memory management + * carefully instead of using the default of strdup_strings=1. + */ +void strmap_init_with_options(struct strmap *map, + int strdup_strings); + +/* + * Remove all entries from the map, releasing any allocated resources. + */ +void strmap_clear(struct strmap *map, int free_values); + +/* + * Insert "str" into the map, pointing to "data". + * + * If an entry for "str" already exists, its data pointer is overwritten, and + * the original data pointer returned. Otherwise, returns NULL. + */ +void *strmap_put(struct strmap *map, const char *str, void *data); + +/* + * Return the data pointer mapped by "str", or NULL if the entry does not + * exist. + */ +void *strmap_get(struct strmap *map, const char *str); + +/* + * Return non-zero iff "str" is present in the map. This differs from + * strmap_get() in that it can distinguish entries with a NULL data pointer. + */ +int strmap_contains(struct strmap *map, const char *str); + +#endif /* STRMAP_H */ From b70c82e6edd33aa03afecd52b0265cf1d9762e1b Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Thu, 5 Nov 2020 00:22:39 +0000 Subject: [PATCH 07/15] strmap: add more utility functions This adds a number of additional convienence functions I want/need: * strmap_get_size() * strmap_empty() * strmap_remove() * strmap_for_each_entry() * strmap_get_entry() I suspect the first four are self-explanatory. strmap_get_entry() is similar to strmap_get() except that instead of just returning the void* value that the string maps to, it returns the strmap_entry that contains both the string and the void* value (or NULL if the string isn't in the map). This is helpful because it avoids multiple lookups, e.g. in some cases a caller would need to call: * strmap_contains() to check that the map has an entry for the string * strmap_get() to get the void* value * * strmap_put() to update/overwrite the value If the void* pointer returned really is a pointer, then the last step is unnecessary, but if the void* pointer is just cast to an integer then strmap_put() will be needed. In contrast, one can call strmap_get_entry() and then: * check if the string was in the map by whether the pointer is NULL * access the value via entry->value * directly update entry->value meaning that we can replace two or three hash table lookups with one. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- strmap.c | 20 ++++++++++++++++++++ strmap.h | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/strmap.c b/strmap.c index 53f284eb20..829f1bc095 100644 --- a/strmap.c +++ b/strmap.c @@ -87,6 +87,11 @@ void *strmap_put(struct strmap *map, const char *str, void *data) return old; } +struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str) +{ + return find_strmap_entry(map, str); +} + void *strmap_get(struct strmap *map, const char *str) { struct strmap_entry *entry = find_strmap_entry(map, str); @@ -97,3 +102,18 @@ int strmap_contains(struct strmap *map, const char *str) { return find_strmap_entry(map, str) != NULL; } + +void strmap_remove(struct strmap *map, const char *str, int free_value) +{ + struct strmap_entry entry, *ret; + hashmap_entry_init(&entry.ent, strhash(str)); + entry.key = str; + ret = hashmap_remove_entry(&map->map, &entry, ent, NULL); + if (!ret) + return; + if (free_value) + free(ret->value); + if (map->strdup_strings) + free((char*)ret->key); + free(ret); +} diff --git a/strmap.h b/strmap.h index 96888c23ad..f74bc582e4 100644 --- a/strmap.h +++ b/strmap.h @@ -50,6 +50,12 @@ void strmap_clear(struct strmap *map, int free_values); */ void *strmap_put(struct strmap *map, const char *str, void *data); +/* + * Return the strmap_entry mapped by "str", or NULL if there is not such + * an item in map. + */ +struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str); + /* * Return the data pointer mapped by "str", or NULL if the entry does not * exist. @@ -62,4 +68,32 @@ void *strmap_get(struct strmap *map, const char *str); */ int strmap_contains(struct strmap *map, const char *str); +/* + * Remove the given entry from the strmap. If the string isn't in the + * strmap, the map is not altered. + */ +void strmap_remove(struct strmap *map, const char *str, int free_value); + +/* + * Return how many entries the strmap has. + */ +static inline unsigned int strmap_get_size(struct strmap *map) +{ + return hashmap_get_size(&map->map); +} + +/* + * Return whether the strmap is empty. + */ +static inline int strmap_empty(struct strmap *map) +{ + return strmap_get_size(map) == 0; +} + +/* + * iterate through @map using @iter, @var is a pointer to a type strmap_entry + */ +#define strmap_for_each_entry(mystrmap, iter, var) \ + hashmap_for_each_entry(&(mystrmap)->map, iter, var, ent) + #endif /* STRMAP_H */ From 6ccdfc2a206d8266ad7613999a0d3f6acdf44c89 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Thu, 5 Nov 2020 00:22:40 +0000 Subject: [PATCH 08/15] strmap: enable faster clearing and reusing of strmaps When strmaps are used heavily, such as is done by my new merge-ort algorithm, and strmaps need to be cleared but then re-used (because of e.g. picking multiple commits to cherry-pick, or due to a recursive merge having several different merges while recursing), free-ing and reallocating map->table repeatedly can add up in time, especially since it will likely be reallocated to a much smaller size but the previous merge provides a good guide to the right size to use for the next merge. Introduce strmap_partial_clear() to take advantage of this type of situation; it will act similar to strmap_clear() except that map->table's entries are zeroed instead of map->table being free'd. Making use of this function reduced the cost of clear_or_reinit_internal_opts() by about 20% in mert-ort, and dropped the overall runtime of my rebase testcase by just under 2%. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- strmap.c | 6 ++++++ strmap.h | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/strmap.c b/strmap.c index 829f1bc095..c410c5241a 100644 --- a/strmap.c +++ b/strmap.c @@ -64,6 +64,12 @@ void strmap_clear(struct strmap *map, int free_values) hashmap_clear(&map->map); } +void strmap_partial_clear(struct strmap *map, int free_values) +{ + strmap_free_entries_(map, free_values); + hashmap_partial_clear(&map->map); +} + void *strmap_put(struct strmap *map, const char *str, void *data) { struct strmap_entry *entry = find_strmap_entry(map, str); diff --git a/strmap.h b/strmap.h index f74bc582e4..c14fcee148 100644 --- a/strmap.h +++ b/strmap.h @@ -42,6 +42,12 @@ void strmap_init_with_options(struct strmap *map, */ void strmap_clear(struct strmap *map, int free_values); +/* + * Similar to strmap_clear() but leaves map->map->table allocated and + * pre-sized so that subsequent uses won't need as many rehashings. + */ +void strmap_partial_clear(struct strmap *map, int free_values); + /* * Insert "str" into the map, pointing to "data". * From 4fa1d501f775253fe70bf6c6a00fe8156c8c61c7 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Thu, 5 Nov 2020 00:22:41 +0000 Subject: [PATCH 09/15] strmap: add functions facilitating use as a string->int map Although strmap could be used as a string->int map, one either had to allocate an int for every entry and then deallocate later, or one had to do a bunch of casting between (void*) and (intptr_t). Add some special functions that do the casting. Also, rename put->set for such wrapper functions since 'put' implied there may be some deallocation needed if the string was already found in the map, which isn't the case when we're storing an int value directly in the void* slot instead of using the void* slot as a pointer to data. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- strmap.c | 11 +++++++ strmap.h | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/strmap.c b/strmap.c index c410c5241a..0d10a884b5 100644 --- a/strmap.c +++ b/strmap.c @@ -123,3 +123,14 @@ void strmap_remove(struct strmap *map, const char *str, int free_value) free((char*)ret->key); free(ret); } + +void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt) +{ + struct strmap_entry *entry = find_strmap_entry(&map->map, str); + if (entry) { + intptr_t *whence = (intptr_t*)&entry->value; + *whence += amt; + } + else + strintmap_set(map, str, map->default_value + amt); +} diff --git a/strmap.h b/strmap.h index c14fcee148..56a5cdb864 100644 --- a/strmap.h +++ b/strmap.h @@ -23,6 +23,10 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data, .map = HASHMAP_INIT(cmp_strmap_entry, NULL), \ .strdup_strings = 1, \ } +#define STRINTMAP_INIT { \ + .map = STRMAP_INIT, \ + .default_value = 0, \ + } /* * Initialize the members of the strmap. Any keys added to the strmap will @@ -102,4 +106,94 @@ static inline int strmap_empty(struct strmap *map) #define strmap_for_each_entry(mystrmap, iter, var) \ hashmap_for_each_entry(&(mystrmap)->map, iter, var, ent) + +/* + * strintmap: + * A map of string -> int, typecasting the void* of strmap to an int. + * + * Primary differences: + * 1) Since the void* value is just an int in disguise, there is no value + * to free. (Thus one fewer argument to strintmap_clear) + * 2) strintmap_get() returns an int, or returns the default_value if the + * key is not found in the strintmap. + * 3) No strmap_put() equivalent; strintmap_set() and strintmap_incr() + * instead. + */ + +struct strintmap { + struct strmap map; + int default_value; +}; + +#define strintmap_for_each_entry(mystrmap, iter, var) \ + strmap_for_each_entry(&(mystrmap)->map, iter, var) + +static inline void strintmap_init(struct strintmap *map, int default_value) +{ + strmap_init(&map->map); + map->default_value = default_value; +} + +static inline void strintmap_init_with_options(struct strintmap *map, + int default_value, + int strdup_strings) +{ + strmap_init_with_options(&map->map, strdup_strings); + map->default_value = default_value; +} + +static inline void strintmap_clear(struct strintmap *map) +{ + strmap_clear(&map->map, 0); +} + +static inline void strintmap_partial_clear(struct strintmap *map) +{ + strmap_partial_clear(&map->map, 0); +} + +static inline int strintmap_contains(struct strintmap *map, const char *str) +{ + return strmap_contains(&map->map, str); +} + +static inline void strintmap_remove(struct strintmap *map, const char *str) +{ + return strmap_remove(&map->map, str, 0); +} + +static inline int strintmap_empty(struct strintmap *map) +{ + return strmap_empty(&map->map); +} + +static inline unsigned int strintmap_get_size(struct strintmap *map) +{ + return strmap_get_size(&map->map); +} + +/* + * Returns the value for str in the map. If str isn't found in the map, + * the map's default_value is returned. + */ +static inline int strintmap_get(struct strintmap *map, const char *str) +{ + struct strmap_entry *result = strmap_get_entry(&map->map, str); + if (!result) + return map->default_value; + return (intptr_t)result->value; +} + +static inline void strintmap_set(struct strintmap *map, const char *str, + intptr_t v) +{ + strmap_put(&map->map, str, (void *)v); +} + +/* + * Increment the value for str by amt. If str isn't in the map, add it and + * set its value to default_value + amt. + */ +void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt); + #endif /* STRMAP_H */ From 6abd22065ccbec054db5af85296dd1167c670eda Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Fri, 6 Nov 2020 00:24:53 +0000 Subject: [PATCH 10/15] strmap: split create_entry() out of strmap_put() This will facilitate adding entries to a strmap subtype in ways that differ slightly from that of strmap_put(). Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- strmap.c | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/strmap.c b/strmap.c index 0d10a884b5..dc84c57c07 100644 --- a/strmap.c +++ b/strmap.c @@ -70,27 +70,36 @@ void strmap_partial_clear(struct strmap *map, int free_values) hashmap_partial_clear(&map->map); } +static struct strmap_entry *create_entry(struct strmap *map, + const char *str, + void *data) +{ + struct strmap_entry *entry; + const char *key = str; + + entry = xmalloc(sizeof(*entry)); + hashmap_entry_init(&entry->ent, strhash(str)); + + if (map->strdup_strings) + key = xstrdup(str); + entry->key = key; + entry->value = data; + return entry; +} + void *strmap_put(struct strmap *map, const char *str, void *data) { struct strmap_entry *entry = find_strmap_entry(map, str); - void *old = NULL; if (entry) { - old = entry->value; + void *old = entry->value; entry->value = data; - } else { - const char *key = str; - - entry = xmalloc(sizeof(*entry)); - hashmap_entry_init(&entry->ent, strhash(str)); - - if (map->strdup_strings) - key = xstrdup(str); - entry->key = key; - entry->value = data; - hashmap_add(&map->map, &entry->ent); + return old; } - return old; + + entry = create_entry(map, str, data); + hashmap_add(&map->map, &entry->ent); + return NULL; } struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str) From 1201eb628ac753af5751258466df5f964bdc9f17 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Fri, 6 Nov 2020 00:24:54 +0000 Subject: [PATCH 11/15] strmap: add a strset sub-type Similar to adding strintmap for special-casing a string -> int mapping, add a strset type for cases where we really are only interested in using strmap for storing a set rather than a mapping. In this case, we'll always just store NULL for the value but the different struct type makes it clearer than code comments how a variable is intended to be used. The difference in usage also results in some differences in API: a few things that aren't necessary or meaningful are dropped (namely, the free_values argument to *_clear(), and the *_get() function), and strset_add() is chosen as the API instead of strset_put(). Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- strmap.c | 17 +++++++++++++++ strmap.h | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/strmap.c b/strmap.c index dc84c57c07..3784865745 100644 --- a/strmap.c +++ b/strmap.c @@ -143,3 +143,20 @@ void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt) else strintmap_set(map, str, map->default_value + amt); } + +int strset_add(struct strset *set, const char *str) +{ + /* + * Cannot use strmap_put() because it'll return NULL in both cases: + * - cannot find str: NULL means "not found" + * - does find str: NULL is the value associated with str + */ + struct strmap_entry *entry = find_strmap_entry(&set->map, str); + + if (entry) + return 0; + + entry = create_entry(&set->map, str, NULL); + hashmap_add(&set->map.map, &entry->ent); + return 1; +} diff --git a/strmap.h b/strmap.h index 56a5cdb864..c8c4d7c932 100644 --- a/strmap.h +++ b/strmap.h @@ -27,6 +27,7 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data, .map = STRMAP_INIT, \ .default_value = 0, \ } +#define STRSET_INIT { .map = STRMAP_INIT } /* * Initialize the members of the strmap. Any keys added to the strmap will @@ -196,4 +197,66 @@ static inline void strintmap_set(struct strintmap *map, const char *str, */ void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt); +/* + * strset: + * A set of strings. + * + * Primary differences with strmap: + * 1) The value is always NULL, and ignored. As there is no value to free, + * there is one fewer argument to strset_clear + * 2) No strset_get() because there is no value. + * 3) No strset_put(); use strset_add() instead. + */ + +struct strset { + struct strmap map; +}; + +#define strset_for_each_entry(mystrset, iter, var) \ + strmap_for_each_entry(&(mystrset)->map, iter, var) + +static inline void strset_init(struct strset *set) +{ + strmap_init(&set->map); +} + +static inline void strset_init_with_options(struct strset *set, + int strdup_strings) +{ + strmap_init_with_options(&set->map, strdup_strings); +} + +static inline void strset_clear(struct strset *set) +{ + strmap_clear(&set->map, 0); +} + +static inline void strset_partial_clear(struct strset *set) +{ + strmap_partial_clear(&set->map, 0); +} + +static inline int strset_contains(struct strset *set, const char *str) +{ + return strmap_contains(&set->map, str); +} + +static inline void strset_remove(struct strset *set, const char *str) +{ + return strmap_remove(&set->map, str, 0); +} + +static inline int strset_empty(struct strset *set) +{ + return strmap_empty(&set->map); +} + +static inline unsigned int strset_get_size(struct strset *set) +{ + return strmap_get_size(&set->map); +} + +/* Returns 1 if str is added to the set; returns 0 if str was already in set */ +int strset_add(struct strset *set, const char *str); + #endif /* STRMAP_H */ From a208ec1f0b654390ad06372c53b7ffe785052d98 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 11 Nov 2020 20:02:18 +0000 Subject: [PATCH 12/15] strmap: enable allocations to come from a mem_pool For heavy users of strmaps, allowing the keys and entries to be allocated from a memory pool can provide significant overhead savings. Add an option to strmap_init_with_options() to specify a memory pool. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- strmap.c | 31 ++++++++++++++++++++++--------- strmap.h | 11 ++++++++--- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/strmap.c b/strmap.c index 3784865745..139afb9d4b 100644 --- a/strmap.c +++ b/strmap.c @@ -1,5 +1,6 @@ #include "git-compat-util.h" #include "strmap.h" +#include "mem-pool.h" int cmp_strmap_entry(const void *hashmap_cmp_fn_data, const struct hashmap_entry *entry1, @@ -24,13 +25,15 @@ static struct strmap_entry *find_strmap_entry(struct strmap *map, void strmap_init(struct strmap *map) { - strmap_init_with_options(map, 1); + strmap_init_with_options(map, NULL, 1); } void strmap_init_with_options(struct strmap *map, + struct mem_pool *pool, int strdup_strings) { hashmap_init(&map->map, cmp_strmap_entry, NULL, 0); + map->pool = pool; map->strdup_strings = strdup_strings; } @@ -42,6 +45,10 @@ static void strmap_free_entries_(struct strmap *map, int free_values) if (!map) return; + if (!free_values && map->pool) + /* Memory other than util is owned by and freed with the pool */ + return; + /* * We need to iterate over the hashmap entries and free * e->key and e->value ourselves; hashmap has no API to @@ -52,9 +59,11 @@ static void strmap_free_entries_(struct strmap *map, int free_values) hashmap_for_each_entry(&map->map, &iter, e, ent) { if (free_values) free(e->value); - if (map->strdup_strings) - free((char*)e->key); - free(e); + if (!map->pool) { + if (map->strdup_strings) + free((char*)e->key); + free(e); + } } } @@ -77,11 +86,13 @@ static struct strmap_entry *create_entry(struct strmap *map, struct strmap_entry *entry; const char *key = str; - entry = xmalloc(sizeof(*entry)); + entry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry)) + : xmalloc(sizeof(*entry)); hashmap_entry_init(&entry->ent, strhash(str)); if (map->strdup_strings) - key = xstrdup(str); + key = map->pool ? mem_pool_strdup(map->pool, str) + : xstrdup(str); entry->key = key; entry->value = data; return entry; @@ -128,9 +139,11 @@ void strmap_remove(struct strmap *map, const char *str, int free_value) return; if (free_value) free(ret->value); - if (map->strdup_strings) - free((char*)ret->key); - free(ret); + if (!map->pool) { + if (map->strdup_strings) + free((char*)ret->key); + free(ret); + } } void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt) diff --git a/strmap.h b/strmap.h index c8c4d7c932..8745b7ceb1 100644 --- a/strmap.h +++ b/strmap.h @@ -3,8 +3,10 @@ #include "hashmap.h" +struct mem_pool; struct strmap { struct hashmap map; + struct mem_pool *pool; unsigned int strdup_strings:1; }; @@ -37,9 +39,10 @@ void strmap_init(struct strmap *map); /* * Same as strmap_init, but for those who want to control the memory management - * carefully instead of using the default of strdup_strings=1. + * carefully instead of using the default of strdup_strings=1 and pool=NULL. */ void strmap_init_with_options(struct strmap *map, + struct mem_pool *pool, int strdup_strings); /* @@ -137,9 +140,10 @@ static inline void strintmap_init(struct strintmap *map, int default_value) static inline void strintmap_init_with_options(struct strintmap *map, int default_value, + struct mem_pool *pool, int strdup_strings) { - strmap_init_with_options(&map->map, strdup_strings); + strmap_init_with_options(&map->map, pool, strdup_strings); map->default_value = default_value; } @@ -221,9 +225,10 @@ static inline void strset_init(struct strset *set) } static inline void strset_init_with_options(struct strset *set, + struct mem_pool *pool, int strdup_strings) { - strmap_init_with_options(&set->map, strdup_strings); + strmap_init_with_options(&set->map, pool, strdup_strings); } static inline void strset_clear(struct strset *set) From 23a276a9c4c8945aadbc323e12c970816eb43c27 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 11 Nov 2020 20:02:19 +0000 Subject: [PATCH 13/15] strmap: take advantage of FLEXPTR_ALLOC_STR when relevant By default, we do not use a mempool and strdup_strings is true; in this case, we can avoid both an extra allocation and an extra free by just over-allocating for the strmap_entry leaving enough space at the end to copy the key. FLEXPTR_ALLOC_STR exists for exactly this purpose, so make use of it. Also, adjust the case when we are using a memory pool and strdup_strings is true to just do one allocation from the memory pool instead of two so that the strmap_clear() and strmap_remove() code can just avoid freeing the key in all cases. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- strmap.c | 35 +++++++++++++++++++---------------- strmap.h | 1 + 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/strmap.c b/strmap.c index 139afb9d4b..4fb9f6100e 100644 --- a/strmap.c +++ b/strmap.c @@ -59,11 +59,8 @@ static void strmap_free_entries_(struct strmap *map, int free_values) hashmap_for_each_entry(&map->map, &iter, e, ent) { if (free_values) free(e->value); - if (!map->pool) { - if (map->strdup_strings) - free((char*)e->key); + if (!map->pool) free(e); - } } } @@ -84,16 +81,25 @@ static struct strmap_entry *create_entry(struct strmap *map, void *data) { struct strmap_entry *entry; - const char *key = str; - entry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry)) - : xmalloc(sizeof(*entry)); + if (map->strdup_strings) { + if (!map->pool) { + FLEXPTR_ALLOC_STR(entry, key, str); + } else { + size_t len = st_add(strlen(str), 1); /* include NUL */ + entry = mem_pool_alloc(map->pool, + st_add(sizeof(*entry), len)); + memcpy(entry + 1, str, len); + entry->key = (void *)(entry + 1); + } + } else if (!map->pool) { + entry = xmalloc(sizeof(*entry)); + } else { + entry = mem_pool_alloc(map->pool, sizeof(*entry)); + } hashmap_entry_init(&entry->ent, strhash(str)); - - if (map->strdup_strings) - key = map->pool ? mem_pool_strdup(map->pool, str) - : xstrdup(str); - entry->key = key; + if (!map->strdup_strings) + entry->key = str; entry->value = data; return entry; } @@ -139,11 +145,8 @@ void strmap_remove(struct strmap *map, const char *str, int free_value) return; if (free_value) free(ret->value); - if (!map->pool) { - if (map->strdup_strings) - free((char*)ret->key); + if (!map->pool) free(ret); - } } void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt) diff --git a/strmap.h b/strmap.h index 8745b7ceb1..c4c104411b 100644 --- a/strmap.h +++ b/strmap.h @@ -14,6 +14,7 @@ struct strmap_entry { struct hashmap_entry ent; const char *key; void *value; + /* strmap_entry may be allocated extra space to store the key at end */ }; int cmp_strmap_entry(const void *hashmap_cmp_fn_data, From b19315d8ab46ae50410dba9228a4c08ae5c73e38 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 11 Nov 2020 20:02:20 +0000 Subject: [PATCH 14/15] Use new HASHMAP_INIT macro to simplify hashmap initialization Now that hashamp has lazy initialization and a HASHMAP_INIT macro, hashmaps allocated on the stack can be initialized without a call to hashmap_init() and in some cases makes the code a bit shorter. Convert some callsites over to take advantage of this. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- attr.c | 26 ++++++++------------------ bloom.c | 3 +-- builtin/difftool.c | 9 ++++----- range-diff.c | 4 +--- revision.c | 9 +-------- t/helper/test-hashmap.c | 3 +-- 6 files changed, 16 insertions(+), 38 deletions(-) diff --git a/attr.c b/attr.c index a826b2ef1f..4ef85d668b 100644 --- a/attr.c +++ b/attr.c @@ -52,13 +52,6 @@ static inline void hashmap_unlock(struct attr_hashmap *map) pthread_mutex_unlock(&map->mutex); } -/* - * The global dictionary of all interned attributes. This - * is a singleton object which is shared between threads. - * Access to this dictionary must be surrounded with a mutex. - */ -static struct attr_hashmap g_attr_hashmap; - /* The container for objects stored in "struct attr_hashmap" */ struct attr_hash_entry { struct hashmap_entry ent; @@ -80,11 +73,14 @@ static int attr_hash_entry_cmp(const void *unused_cmp_data, return (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen); } -/* Initialize an 'attr_hashmap' object */ -static void attr_hashmap_init(struct attr_hashmap *map) -{ - hashmap_init(&map->map, attr_hash_entry_cmp, NULL, 0); -} +/* + * The global dictionary of all interned attributes. This + * is a singleton object which is shared between threads. + * Access to this dictionary must be surrounded with a mutex. + */ +static struct attr_hashmap g_attr_hashmap = { + HASHMAP_INIT(attr_hash_entry_cmp, NULL) +}; /* * Retrieve the 'value' stored in a hashmap given the provided 'key'. @@ -96,9 +92,6 @@ static void *attr_hashmap_get(struct attr_hashmap *map, struct attr_hash_entry k; struct attr_hash_entry *e; - if (!map->map.tablesize) - attr_hashmap_init(map); - hashmap_entry_init(&k.ent, memhash(key, keylen)); k.key = key; k.keylen = keylen; @@ -114,9 +107,6 @@ static void attr_hashmap_add(struct attr_hashmap *map, { struct attr_hash_entry *e; - if (!map->map.tablesize) - attr_hashmap_init(map); - e = xmalloc(sizeof(struct attr_hash_entry)); hashmap_entry_init(&e->ent, memhash(key, keylen)); e->key = key; diff --git a/bloom.c b/bloom.c index 719c313a1c..b176f28f53 100644 --- a/bloom.c +++ b/bloom.c @@ -229,10 +229,9 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, diffcore_std(&diffopt); if (diff_queued_diff.nr <= settings->max_changed_paths) { - struct hashmap pathmap; + struct hashmap pathmap = HASHMAP_INIT(pathmap_cmp, NULL); struct pathmap_hash_entry *e; struct hashmap_iter iter; - hashmap_init(&pathmap, pathmap_cmp, NULL, 0); for (i = 0; i < diff_queued_diff.nr; i++) { const char *path = diff_queued_diff.queue[i]->two->path; diff --git a/builtin/difftool.c b/builtin/difftool.c index 7ac432b881..6e18e623fd 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -342,7 +342,10 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, const char *workdir, *tmp; int ret = 0, i; FILE *fp; - struct hashmap working_tree_dups, submodules, symlinks2; + struct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp, + NULL); + struct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL); + struct hashmap symlinks2 = HASHMAP_INIT(pair_cmp, NULL); struct hashmap_iter iter; struct pair_entry *entry; struct index_state wtindex; @@ -383,10 +386,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, rdir_len = rdir.len; wtdir_len = wtdir.len; - hashmap_init(&working_tree_dups, working_tree_entry_cmp, NULL, 0); - hashmap_init(&submodules, pair_cmp, NULL, 0); - hashmap_init(&symlinks2, pair_cmp, NULL, 0); - child.no_stdin = 1; child.git_cmd = 1; child.use_shell = 0; diff --git a/range-diff.c b/range-diff.c index befeecae44..b9950f10c8 100644 --- a/range-diff.c +++ b/range-diff.c @@ -232,11 +232,9 @@ static int patch_util_cmp(const void *dummy, const struct patch_util *a, static void find_exact_matches(struct string_list *a, struct string_list *b) { - struct hashmap map; + struct hashmap map = HASHMAP_INIT((hashmap_cmp_fn)patch_util_cmp, NULL); int i; - hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0); - /* First, add the patches of a to a hash map */ for (i = 0; i < a->nr; i++) { struct patch_util *util = a->items[i].util; diff --git a/revision.c b/revision.c index f27649d45d..c6e169e3eb 100644 --- a/revision.c +++ b/revision.c @@ -124,11 +124,6 @@ static int path_and_oids_cmp(const void *hashmap_cmp_fn_data, return strcmp(e1->path, e2->path); } -static void paths_and_oids_init(struct hashmap *map) -{ - hashmap_init(map, path_and_oids_cmp, NULL, 0); -} - static void paths_and_oids_clear(struct hashmap *map) { struct hashmap_iter iter; @@ -213,7 +208,7 @@ void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees) { unsigned has_interesting = 0, has_uninteresting = 0; - struct hashmap map; + struct hashmap map = HASHMAP_INIT(path_and_oids_cmp, NULL); struct hashmap_iter map_iter; struct path_and_oids_entry *entry; struct object_id *oid; @@ -237,8 +232,6 @@ void mark_trees_uninteresting_sparse(struct repository *r, if (!has_uninteresting || !has_interesting) return; - paths_and_oids_init(&map); - oidset_iter_init(trees, &iter); while ((oid = oidset_iter_next(&iter))) { struct tree *tree = lookup_tree(r, oid); diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c index 2475663b49..36ff07bd4b 100644 --- a/t/helper/test-hashmap.c +++ b/t/helper/test-hashmap.c @@ -151,12 +151,11 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) int cmd__hashmap(int argc, const char **argv) { struct strbuf line = STRBUF_INIT; - struct hashmap map; int icase; + struct hashmap map = HASHMAP_INIT(test_entry_cmp, &icase); /* init hash map */ icase = argc > 1 && !strcmp("ignorecase", argv[1]); - hashmap_init(&map, test_entry_cmp, &icase, 0); /* process commands from stdin */ while (strbuf_getline(&line, stdin) != EOF) { From 449a900969d0f060d054e5a13084bed318da3a31 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 11 Nov 2020 20:02:21 +0000 Subject: [PATCH 15/15] shortlog: use strset from strmap.h Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- builtin/shortlog.c | 61 +++------------------------------------------- 1 file changed, 4 insertions(+), 57 deletions(-) diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 83f0a739b4..c52e4ccd19 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -10,6 +10,7 @@ #include "shortlog.h" #include "parse-options.h" #include "trailer.h" +#include "strmap.h" static char const * const shortlog_usage[] = { N_("git shortlog [] [] [[--] ...]"), @@ -169,60 +170,6 @@ static void read_from_stdin(struct shortlog *log) strbuf_release(&oneline); } -struct strset_item { - struct hashmap_entry ent; - char value[FLEX_ARRAY]; -}; - -struct strset { - struct hashmap map; -}; - -#define STRSET_INIT { { NULL } } - -static int strset_item_hashcmp(const void *hash_data, - const struct hashmap_entry *entry, - const struct hashmap_entry *entry_or_key, - const void *keydata) -{ - const struct strset_item *a, *b; - - a = container_of(entry, const struct strset_item, ent); - if (keydata) - return strcmp(a->value, keydata); - - b = container_of(entry_or_key, const struct strset_item, ent); - return strcmp(a->value, b->value); -} - -/* - * Adds "str" to the set if it was not already present; returns true if it was - * already there. - */ -static int strset_check_and_add(struct strset *ss, const char *str) -{ - unsigned int hash = strhash(str); - struct strset_item *item; - - if (!ss->map.table) - hashmap_init(&ss->map, strset_item_hashcmp, NULL, 0); - - if (hashmap_get_from_hash(&ss->map, hash, str)) - return 1; - - FLEX_ALLOC_STR(item, value, str); - hashmap_entry_init(&item->ent, hash); - hashmap_add(&ss->map, &item->ent); - return 0; -} - -static void strset_clear(struct strset *ss) -{ - if (!ss->map.table) - return; - hashmap_clear_and_free(&ss->map, struct strset_item, ent); -} - static void insert_records_from_trailers(struct shortlog *log, struct strset *dups, struct commit *commit, @@ -253,7 +200,7 @@ static void insert_records_from_trailers(struct shortlog *log, if (!parse_ident(log, &ident, value)) value = ident.buf; - if (strset_check_and_add(dups, value)) + if (!strset_add(dups, value)) continue; insert_one_record(log, value, oneline); } @@ -291,7 +238,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) log->email ? "%aN <%aE>" : "%aN", &ident, &ctx); if (!HAS_MULTI_BITS(log->groups) || - !strset_check_and_add(&dups, ident.buf)) + strset_add(&dups, ident.buf)) insert_one_record(log, ident.buf, oneline_str); } if (log->groups & SHORTLOG_GROUP_COMMITTER) { @@ -300,7 +247,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) log->email ? "%cN <%cE>" : "%cN", &ident, &ctx); if (!HAS_MULTI_BITS(log->groups) || - !strset_check_and_add(&dups, ident.buf)) + strset_add(&dups, ident.buf)) insert_one_record(log, ident.buf, oneline_str); } if (log->groups & SHORTLOG_GROUP_TRAILER) {