Add struct strmap and associated utility functions
PATCH v6 8/15 strmap: enable faster clearing and reusing of strmaps
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
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%.
--- strmap.c | 6 ++++++ strmap.h | 6 ++++++ 2 files changed, 12 insertions(+)
strmap.c +6 -0
@@ -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);
strmap.h +6 -0
@@ -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". *
Signature · 1 line
gitgitgadget
PATCH v6 9/15 strmap: add functions facilitating use as a string->int map
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
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.
--- strmap.c | 11 +++++++ strmap.h | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+)
strmap.c +11 -0
@@ -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); +}
strmap.h +94 -0
104 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v6 10/15 strmap: split create_entry() out of strmap_put()
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
This will facilitate adding entries to a strmap subtype in ways that differ slightly from that of strmap_put().
--- strmap.c | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-)
strmap.c +23 -14
@@ -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)
Signature · 1 line
gitgitgadget
PATCH v6 11/15 strmap: add a strset sub-type
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
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().
--- strmap.c | 17 +++++++++++++++ strmap.h | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+)
strmap.c +17 -0
@@ -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 ; +}
strmap.h +63 -0
73 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v6 12/15 strmap: enable allocations to come from a mem_pool
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
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.
--- strmap.c | 31 ++++++++++++++++++++++--------- strmap.h | 11 ++++++++--- 2 files changed, 30 insertions(+), 12 deletions(-)
strmap.c +22 -9
@@ -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)
strmap.h +8 -3
@@ -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)
Signature · 1 line
gitgitgadget
PATCH v6 13/15 strmap: take advantage of FLEXPTR_ALLOC_STR when relevant
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
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.
--- strmap.c | 35 +++++++++++++++++++---------------- strmap.h | 1 + 2 files changed, 20 insertions(+), 16 deletions(-)
strmap.c +19 -16
@@ -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)
strmap.h +1 -0
@@ -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,
Signature · 1 line
gitgitgadget
PATCH v6 14/15 Use new HASHMAP_INIT macro to simplify hashmap initialization
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
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.
--- 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(-)
attr.c +8 -18
@@ -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;
bloom.c +1 -2
@@ -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;
builtin/difftool.c +4 -5
@@ -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 ;
range-diff.c +1 -3
@@ -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;
revision.c +1 -8
@@ -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);
t/helper/test-hashmap.c +1 -2
@@ -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 ) {
Signature · 1 line
gitgitgadget
PATCH v6 15/15 shortlog: use strset from strmap.h
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
--- builtin/shortlog.c | 61 +++------------------------------------------- 1 file changed, 4 insertions(+), 57 deletions(-)
builtin/shortlog.c +4 -57
@@ -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 [<options>] [<revision-range>] [[--] <path>...]" ),
@@ -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 ) {
Signature · 1 line
gitgitgadget
Quoted 6 lines · Elijah Newren via GitGitGadget
Here I introduce new strmap, strintmap, and strset types.
Changes since v5:
* Fixed a typo in forward declaration of struct
mem_pool, spotted by Phillip. (Usage via pointers meant gcc & clang wouldn't complain.)
Yep, this version looks good to me. I think this is probably ready for 'next'.
-Peff