git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH 2/5] strmap: new utility functions

From
Jeff King <peff@peff.net>
Date
Aug 21, 2020, 19:48 UTC
Message-ID
<20200821194857.GD1165@coredump.intra.peff.net>
In-Reply-To
<a86fd5fdcc47baf85046eb07257f4db9f9498084.1598035949.git.gitgitgadget@gmail.com>
On Fri, Aug 21, 2020 at 06:52:26PM +0000, Elijah Newren via GitGitGadget wrote:
Show 6 quoted lines
> From: Elijah Newren <newren@gmail.com>
> 
> 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/

Uh oh. You are encouraging me in the belief that I can send half-baked ideas to the list and somebody will come along and implement them for me. ;)

Show 5 quoted lines
> Peff only included the header, not the implementation, so it isn't clear what
> the structure was he was going to use for the hash entries.  Instead of having
> my str_entry struct have three subfields (the hashmap_entry, the string, and
> the void* value), I made it only have two -- the hashmap_entry and a
> string_list_item, for two reasons:
I'd probably have done:
  struct strmap_entry {
	struct hashmap_entry ent;
	void *value;
	char key[FLEX_ALLOC];
  };

That saves 8 bytes (plus malloc overhead)per item, plus avoids an extra pointer-chase for each item we consider when looking up.

>   1) a strmap is often the data structure we want where string_list has
>      been used in the past.  Using the same building block for
>      individual entries in both makes it easier to adopt and reuse
>      parts of the string_list API in strmap.

I can see that there might be some value in being able to interchange the items for code that's expecting a string_list_item. But I have to wonder if the potential for confusion is worth it. I.e., should that code really be expecting a raw string pointer (possibly with a separate void pointer, or even better an actual typed pointer).

I'll keep an eye out as I read the rest of the series for code which uses this.

Show 11 quoted lines
>   2) In some cases, after doing lots of other work, I want to be able
>      to iterate over the items in my strmap in sorted order.  hashmap
>      obviously doesn't support that, but I wanted to be able to export
>      the strmap to a string_list easily and then use its functions.
>      (Note: I do not need the data structure to both be sorted and have
>      efficient lookup at all times.  If I did, I might use a B-tree
>      instead, as suggested by brian in response to Peff in the thread
>      noted above.  In my case, most strmaps will never need sorting, but
>      in one special case at the very end of a bunch of other work I want
>      to iterate over the items in sorted order without doing any more
>      lookups afterward.)

Hmm. Likewise, I'll keep an eye open for how this works in practice. I do suspect that a B-tree might be a better solution here, but implementing it is non-trivial, and most callers don't care about this property.

If the interim solution is to just dump it to a string_list and sort that, that's really not that bad, assuming it just happens once after we've added everything. I'm not sure there's that big a benefit to using string_list_item internally, since presumably that conversion needs to write a whole new array of string_list_items anyway.

> Also, I removed the STRMAP_INIT macro, since it cannot be used to
> correctly initialize a strmap; the underlying hashmap needs a call to
> hashmap_init() to allocate the hash table first.

Since access to the underlying hashmap happens through strmap functions, they could lazily initialize it. That's how oidmap works.

Show 20 quoted lines
> diff --git a/strmap.c b/strmap.c
> new file mode 100644
> index 0000000000..1c9fdb3b1e
> --- /dev/null
> +++ b/strmap.c
> @@ -0,0 +1,81 @@
> +#include "git-compat-util.h"
> +#include "strmap.h"
> +
> +static int cmp_str_entry(const void *hashmap_cmp_fn_data,
> +			 const struct hashmap_entry *entry1,
> +			 const struct hashmap_entry *entry2,
> +			 const void *keydata)
> +{
> +	const struct str_entry *e1, *e2;
> +
> +	e1 = container_of(entry1, const struct str_entry, ent);
> +	e2 = container_of(entry2, const struct str_entry, ent);
> +	return strcmp(e1->item.string, e2->item.string);
> +}

If you do go the FLEX_ALLOC route, obviously lookups won't want to allocate a str_entry struct for the lookup key. You'd use keydata there (and prefer it over looking at entry2 at all). See remotes_hash_cmp() for an example.

Show 8 quoted lines
> +static struct str_entry *find_str_entry(struct strmap *map,
> +					const char *str)
> +{
> +	struct str_entry entry;
> +	hashmap_entry_init(&entry.ent, strhash(str));
> +	entry.item.string = (char *)str;
> +	return hashmap_get_entry(&map->map, &entry, ent, NULL);
> +}

Casting away constness here is awkward. It could likewise benefit from using keydata, so you wouldn't need to create a temporary string_list_item (which is where the non-constness comes from).

Show 7 quoted lines
> +void strmap_clear(struct strmap *map, int free_util)
> +{
> +	struct hashmap_iter iter;
> +	struct str_entry *e;
> +
> +	if (!map)
> +		return;
In a lazy-init world, this becomes:
  if (!map || !map->map.table)

Of course it would be better still if the hashmap code learned to do the lazy-init stuff itself.

Show 6 quoted lines
> +	hashmap_for_each_entry(&map->map, &iter, e, ent /* member name */) {
> +		free(e->item.string);
> +		if (free_util)
> +			free(e->item.util);
> +	}
> +	hashmap_free_entries(&map->map, struct str_entry, ent);

With a flex-alloc struct, you can avoid the extra string free. But I guess you still wouldn't avoid the loop if you want to support free_entries().

I wonder if it would make the API simpler if the struct knew whether it owned the void pointer values or not. Then you'd do:

  struct strmap foo = { .free_values = 1 };
  ...
  strmap_put(&foo, "key", value);
  ...
  strmap_clear(&foo);

and wouldn't have to remember to do the right thing at clear-time. It is a little less flexible (e.g., if you transfer ownership after a certain point in the code), but I wonder if any callers actually need that (and they could always set the free_values flag then).

Show 8 quoted lines
> +/*
> + * Insert "str" into the map, pointing to "data". A copy of "str" is made, so
> + * it does not need to persist after the this function is called.
> + *
> + * 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)

Minor, but IMHO we should avoid copying the docstrings to the implementation, since it gives two places that people have to remember to update if the API changes.

> +void *strmap_put(struct strmap *map, const char *str, void *data)
> +{
> +	struct str_entry *entry = find_str_entry(map, str);
> +	void *old = NULL;
In a lazy-init world, this is:
  if (!map->map.table) {
	strmap_init(map);
	entry = NULL;
  } else {
        entry = find_str_entry(map, str);
  }

(or just call find_str_entry() in both cases and let it realize there's nothing to find).

Show 10 quoted lines
> +	if (entry) {
> +		old = entry->item.util;
> +		entry->item.util = data;
> +	} else {
> +		entry = xmalloc(sizeof(*entry));
> +		hashmap_entry_init(&entry->ent, strhash(str));
> +		entry->item.string = strdup(str);
> +		entry->item.util = data;
> +		hashmap_add(&map->map, &entry->ent);
> +	}
And in a flex-alloc world, this second block is:
  FLEX_ALLOC_STR(entry, key, str);
  hashmap_entry_init(&entry->ent, strhash(str));
  entry->value = data;
  hashmap_add(&map->map, &entry->ent);
Show 5 quoted lines
> +void *strmap_get(struct strmap *map, const char *str)
> +{
> +	struct str_entry *entry = find_str_entry(map, str);
> +	return entry ? entry->item.util : NULL;
> +}
In a lazy world, this is:
  if (!map->map.table)
          return NULL;
> +int strmap_contains(struct strmap *map, const char *str)
> +{
> +	return find_str_entry(map, str) != NULL;
> +}
And likewise:
  if (!map->map.table)
          return NULL;
It might actually be easier to stick that in find_str_entry().
The rest of it all looked good to me.
-Peff
Previous: Elijah Newren via GitGitGadgetNext: Elijah Newren via GitGitGadget
Message 11 of 144 in “Add struct strmap and associated utility functions”
  1. 0/5 Add struct strmap and associated utility functionsElijah Newren via GitGitGadget, Aug 21, 2020
  2. 3/5 strmap: add more utility functionsElijah Newren via GitGitGadget, Aug 21, 2020
  3. Jeff KingAug 21, 2020
  4. 1/5 hashmap: add usage documentation explaining hashmap_free[_entries]()Elijah Newren via GitGitGadget, Aug 21, 2020
  5. Jeff KingAug 21, 2020
  6. 5/5 strmap: add functions facilitating use as a string->int mapElijah Newren via GitGitGadget, Aug 21, 2020
  7. Jeff KingAug 21, 2020
  8. Elijah NewrenAug 21, 2020
  9. Jeff KingAug 21, 2020
  10. 2/5 strmap: new utility functionsElijah Newren via GitGitGadget, Aug 21, 2020
  11. Jeff KingAug 21, 2020
  12. 4/5 strmap: add strdup_strings optionElijah Newren via GitGitGadget, Aug 21, 2020
  13. Jeff KingAug 21, 2020
  14. Elijah NewrenAug 21, 2020
  15. Jeff KingAug 21, 2020
  16. Elijah NewrenAug 21, 2020
  17. Jeff KingAug 28, 2020
  18. Elijah NewrenAug 28, 2020
  19. Jeff KingAug 21, 2020
  20. Elijah NewrenAug 21, 2020
  21. Elijah NewrenAug 21, 2020
  22. Jeff KingAug 28, 2020
  23. Elijah NewrenAug 28, 2020
  24. Jeff KingSep 1, 2020
  25. 00/10 Add struct strmap and associated utility functionsElijah Newren via GitGitGadget, Oct 13, 2020
  26. 01/10 hashmap: add usage documentation explaining hashmap_free[_entries]()Elijah Newren via GitGitGadget, Oct 13, 2020
  27. Jeff KingOct 30, 2020
  28. Elijah NewrenOct 30, 2020
  29. Jeff KingNov 3, 2020
  30. Elijah NewrenNov 3, 2020
  31. 05/10 strmap: new utility functionsElijah Newren via GitGitGadget, Oct 13, 2020
  32. Jeff KingOct 30, 2020
  33. Elijah NewrenOct 30, 2020
  34. 04/10 hashmap: introduce a new hashmap_partial_clear()Elijah Newren via GitGitGadget, Oct 13, 2020
  35. Jeff KingOct 30, 2020
  36. Elijah NewrenOct 30, 2020
  37. Jeff KingNov 3, 2020
  38. 03/10 hashmap: allow re-use after hashmap_free()Elijah Newren via GitGitGadget, Oct 13, 2020
  39. Jeff KingOct 30, 2020
  40. Elijah NewrenOct 30, 2020
  41. Jeff KingNov 3, 2020
  42. Elijah NewrenNov 3, 2020
  43. 02/10 hashmap: adjust spacing to fix argument alignmentElijah Newren via GitGitGadget, Oct 13, 2020
  44. Jeff KingOct 30, 2020
  45. 07/10 strmap: enable faster clearing and reusing of strmapsElijah Newren via GitGitGadget, Oct 13, 2020
  46. Jeff KingOct 30, 2020
  47. 10/10 strmap: enable allocations to come from a mem_poolElijah Newren via GitGitGadget, Oct 13, 2020
  48. Jeff KingOct 30, 2020
  49. Elijah NewrenOct 30, 2020
  50. Jeff KingNov 3, 2020
  51. 06/10 strmap: add more utility functionsElijah Newren via GitGitGadget, Oct 13, 2020
  52. Jeff KingOct 30, 2020
  53. Elijah NewrenOct 30, 2020
  54. Jeff KingNov 3, 2020
  55. 08/10 strmap: add functions facilitating use as a string->int mapElijah Newren via GitGitGadget, Oct 13, 2020
  56. Jeff KingOct 30, 2020
  57. Elijah NewrenOct 30, 2020
  58. Jeff KingNov 3, 2020
  59. Elijah NewrenNov 3, 2020
  60. 09/10 strmap: add a strset sub-typeElijah Newren via GitGitGadget, Oct 13, 2020
  61. Jeff KingOct 30, 2020
  62. Elijah NewrenOct 30, 2020
  63. 00/13 Add struct strmap and associated utility functionsElijah Newren via GitGitGadget, Nov 2, 2020
  64. 01/13 hashmap: add usage documentation explaining hashmap_free[_entries]()Elijah Newren via GitGitGadget, Nov 2, 2020
  65. 02/13 hashmap: adjust spacing to fix argument alignmentElijah Newren via GitGitGadget, Nov 2, 2020
  66. 04/13 hashmap: introduce a new hashmap_partial_clear()Elijah Newren via GitGitGadget, Nov 2, 2020
  67. 03/13 hashmap: allow re-use after hashmap_free()Elijah Newren via GitGitGadget, Nov 2, 2020
  68. 05/13 hashmap: provide deallocation function namesElijah Newren via GitGitGadget, Nov 2, 2020
  69. 09/13 strmap: add functions facilitating use as a string->int mapElijah Newren via GitGitGadget, Nov 2, 2020
  70. Jeff KingNov 4, 2020
  71. 11/13 strmap: enable allocations to come from a mem_poolElijah Newren via GitGitGadget, Nov 2, 2020
  72. 10/13 strmap: add a strset sub-typeElijah Newren via GitGitGadget, Nov 2, 2020
  73. Jeff KingNov 4, 2020
  74. 13/13 Use new HASHMAP_INIT macro to simplify hashmap initializationElijah Newren via GitGitGadget, Nov 2, 2020
  75. Jeff KingNov 4, 2020
  76. 07/13 strmap: add more utility functionsElijah Newren via GitGitGadget, Nov 2, 2020
  77. Jeff KingNov 4, 2020
  78. Elijah NewrenNov 4, 2020
  79. 08/13 strmap: enable faster clearing and reusing of strmapsElijah Newren via GitGitGadget, Nov 2, 2020
  80. 06/13 strmap: new utility functionsElijah Newren via GitGitGadget, Nov 2, 2020
  81. 12/13 strmap: take advantage of FLEXPTR_ALLOC_STR when relevantElijah Newren via GitGitGadget, Nov 2, 2020
  82. Jeff KingNov 4, 2020
  83. Jeff KingNov 4, 2020
  84. Elijah NewrenNov 4, 2020
  85. 00/13 Add struct strmap and associated utility functionsElijah Newren via GitGitGadget, Nov 5, 2020
  86. 13/13 Use new HASHMAP_INIT macro to simplify hashmap initializationElijah Newren via GitGitGadget, Nov 5, 2020
  87. 11/13 strmap: enable allocations to come from a mem_poolElijah Newren via GitGitGadget, Nov 5, 2020
  88. 10/13 strmap: add a strset sub-typeElijah Newren via GitGitGadget, Nov 5, 2020
  89. 12/13 strmap: take advantage of FLEXPTR_ALLOC_STR when relevantElijah Newren via GitGitGadget, Nov 5, 2020
  90. 07/13 strmap: add more utility functionsElijah Newren via GitGitGadget, Nov 5, 2020
  91. 06/13 strmap: new utility functionsElijah Newren via GitGitGadget, Nov 5, 2020
  92. 09/13 strmap: add functions facilitating use as a string->int mapElijah Newren via GitGitGadget, Nov 5, 2020
  93. 08/13 strmap: enable faster clearing and reusing of strmapsElijah Newren via GitGitGadget, Nov 5, 2020
  94. 04/13 hashmap: introduce a new hashmap_partial_clear()Elijah Newren via GitGitGadget, Nov 5, 2020
  95. 05/13 hashmap: provide deallocation function namesElijah Newren via GitGitGadget, Nov 5, 2020
  96. 02/13 hashmap: adjust spacing to fix argument alignmentElijah Newren via GitGitGadget, Nov 5, 2020
  97. 03/13 hashmap: allow re-use after hashmap_free()Elijah Newren via GitGitGadget, Nov 5, 2020
  98. 01/13 hashmap: add usage documentation explaining hashmap_free[_entries]()Elijah Newren via GitGitGadget, Nov 5, 2020
  99. Jeff KingNov 5, 2020
  100. Junio C HamanoNov 5, 2020
  101. Jeff KingNov 5, 2020
  102. Elijah NewrenNov 5, 2020
  103. Junio C HamanoNov 5, 2020
  104. 00/15 Add struct strmap and associated utility functionsElijah Newren via GitGitGadget, Nov 6, 2020
  105. 01/15 hashmap: add usage documentation explaining hashmap_free[_entries]()Elijah Newren via GitGitGadget, Nov 6, 2020
  106. 02/15 hashmap: adjust spacing to fix argument alignmentElijah Newren via GitGitGadget, Nov 6, 2020
  107. 04/15 hashmap: introduce a new hashmap_partial_clear()Elijah Newren via GitGitGadget, Nov 6, 2020
  108. 03/15 hashmap: allow re-use after hashmap_free()Elijah Newren via GitGitGadget, Nov 6, 2020
  109. 10/15 strmap: split create_entry() out of strmap_put()Elijah Newren via GitGitGadget, Nov 6, 2020
  110. 11/15 strmap: add a strset sub-typeElijah Newren via GitGitGadget, Nov 6, 2020
  111. 05/15 hashmap: provide deallocation function namesElijah Newren via GitGitGadget, Nov 6, 2020
  112. 13/15 strmap: take advantage of FLEXPTR_ALLOC_STR when relevantElijah Newren via GitGitGadget, Nov 6, 2020
  113. 06/15 strmap: new utility functionsElijah Newren via GitGitGadget, Nov 6, 2020
  114. 09/15 strmap: add functions facilitating use as a string->int mapElijah Newren via GitGitGadget, Nov 6, 2020
  115. 08/15 strmap: enable faster clearing and reusing of strmapsElijah Newren via GitGitGadget, Nov 6, 2020
  116. 12/15 strmap: enable allocations to come from a mem_poolElijah Newren via GitGitGadget, Nov 6, 2020
  117. Phillip WoodNov 11, 2020
  118. Elijah NewrenNov 11, 2020
  119. Jeff KingNov 11, 2020
  120. Chris TorekNov 11, 2020
  121. 07/15 strmap: add more utility functionsElijah Newren via GitGitGadget, Nov 6, 2020
  122. 15/15 shortlog: use strset from strmap.hElijah Newren via GitGitGadget, Nov 6, 2020
  123. 14/15 Use new HASHMAP_INIT macro to simplify hashmap initializationElijah Newren via GitGitGadget, Nov 6, 2020
  124. Junio C HamanoNov 6, 2020
  125. Elijah NewrenNov 6, 2020
  126. Jeff KingNov 6, 2020
  127. Junio C HamanoNov 6, 2020
  128. 00/15 Add struct strmap and associated utility functionsElijah Newren via GitGitGadget, Nov 11, 2020
  129. 02/15 hashmap: adjust spacing to fix argument alignmentElijah Newren via GitGitGadget, Nov 11, 2020
  130. 01/15 hashmap: add usage documentation explaining hashmap_free[_entries]()Elijah Newren via GitGitGadget, Nov 11, 2020
  131. 03/15 hashmap: allow re-use after hashmap_free()Elijah Newren via GitGitGadget, Nov 11, 2020
  132. 06/15 strmap: new utility functionsElijah Newren via GitGitGadget, Nov 11, 2020
  133. 05/15 hashmap: provide deallocation function namesElijah Newren via GitGitGadget, Nov 11, 2020
  134. 09/15 strmap: add functions facilitating use as a string->int mapElijah Newren via GitGitGadget, Nov 11, 2020
  135. 08/15 strmap: enable faster clearing and reusing of strmapsElijah Newren via GitGitGadget, Nov 11, 2020
  136. 10/15 strmap: split create_entry() out of strmap_put()Elijah Newren via GitGitGadget, Nov 11, 2020
  137. 11/15 strmap: add a strset sub-typeElijah Newren via GitGitGadget, Nov 11, 2020
  138. 12/15 strmap: enable allocations to come from a mem_poolElijah Newren via GitGitGadget, Nov 11, 2020
  139. 04/15 hashmap: introduce a new hashmap_partial_clear()Elijah Newren via GitGitGadget, Nov 11, 2020
  140. 14/15 Use new HASHMAP_INIT macro to simplify hashmap initializationElijah Newren via GitGitGadget, Nov 11, 2020
  141. 15/15 shortlog: use strset from strmap.hElijah Newren via GitGitGadget, Nov 11, 2020
  142. 13/15 strmap: take advantage of FLEXPTR_ALLOC_STR when relevantElijah Newren via GitGitGadget, Nov 11, 2020
  143. 07/15 strmap: add more utility functionsElijah Newren via GitGitGadget, Nov 11, 2020
  144. Jeff KingNov 11, 2020

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.