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

Re: [PATCH v2 5/5] oidtree: a crit-bit tree for odb_loose_cache

From
René Scharfe <l.s.r@web.de>
Date
Jul 4, 2021, 09:02 UTC
Message-ID
<ab757bce-3b51-afac-312c-ea2e883cf0bf@web.de>
In-Reply-To
<20210629205305.7100-6-e@80x24.org>
Am 29.06.21 um 22:53 schrieb Eric Wong:
Show 18 quoted lines
> This saves 8K per `struct object_directory', meaning it saves
> around 800MB in my case involving 100K alternates (half or more
> of those alternates are unlikely to hold loose objects).
>
> This is implemented in two parts: a generic, allocation-free
> `cbtree' and the `oidtree' wrapper on top of it.  The latter
> provides allocation using alloc_state as a memory pool to
> improve locality and reduce free(3) overhead.
>
> Unlike oid-array, the crit-bit tree does not require sorting.
> Performance is bound by the key length, for oidtree that is
> fixed at sizeof(struct object_id).  There's no need to have
> 256 oidtrees to mitigate the O(n log n) overhead like we did
> with oid-array.
>
> Being a prefix trie, it is natively suited for expanding short
> object IDs via prefix-limited iteration in
> `find_short_object_filename'.
Sounds like a good match.
Show 91 quoted lines
>
> On my busy workstation, p4205 performance seems to be roughly
> unchanged (+/-8%).  Startup with 100K total alternates with no
> loose objects seems around 10-20% faster on a hot cache.
> (800MB in memory savings means more memory for the kernel FS
> cache).
>
> The generic cbtree implementation does impose some extra
> overhead for oidtree in that it uses memcmp(3) on
> "struct object_id" so it wastes cycles comparing 12 extra bytes
> on SHA-1 repositories.  I've not yet explored reducing this
> overhead, but I expect there are many places in our code base
> where we'd want to investigate this.
>
> More information on crit-bit trees: https://cr.yp.to/critbit.html
>
> v2: make oidtree test hash-agnostic
>
> Signed-off-by: Eric Wong <e@80x24.org>
> ---
>  Makefile                |   3 +
>  alloc.c                 |   6 ++
>  alloc.h                 |   1 +
>  cbtree.c                | 167 ++++++++++++++++++++++++++++++++++++++++
>  cbtree.h                |  56 ++++++++++++++
>  object-file.c           |  17 ++--
>  object-name.c           |  28 +++----
>  object-store.h          |   5 +-
>  oidtree.c               |  94 ++++++++++++++++++++++
>  oidtree.h               |  29 +++++++
>  t/helper/test-oidtree.c |  47 +++++++++++
>  t/helper/test-tool.c    |   1 +
>  t/helper/test-tool.h    |   1 +
>  t/t0069-oidtree.sh      |  52 +++++++++++++
>  14 files changed, 478 insertions(+), 29 deletions(-)
>  create mode 100644 cbtree.c
>  create mode 100644 cbtree.h
>  create mode 100644 oidtree.c
>  create mode 100644 oidtree.h
>  create mode 100644 t/helper/test-oidtree.c
>  create mode 100755 t/t0069-oidtree.sh
>
> diff --git a/Makefile b/Makefile
> index c3565fc0f8..a1525978fb 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -722,6 +722,7 @@ TEST_BUILTINS_OBJS += test-mergesort.o
>  TEST_BUILTINS_OBJS += test-mktemp.o
>  TEST_BUILTINS_OBJS += test-oid-array.o
>  TEST_BUILTINS_OBJS += test-oidmap.o
> +TEST_BUILTINS_OBJS += test-oidtree.o
>  TEST_BUILTINS_OBJS += test-online-cpus.o
>  TEST_BUILTINS_OBJS += test-parse-options.o
>  TEST_BUILTINS_OBJS += test-parse-pathspec-file.o
> @@ -845,6 +846,7 @@ LIB_OBJS += branch.o
>  LIB_OBJS += bulk-checkin.o
>  LIB_OBJS += bundle.o
>  LIB_OBJS += cache-tree.o
> +LIB_OBJS += cbtree.o
>  LIB_OBJS += chdir-notify.o
>  LIB_OBJS += checkout.o
>  LIB_OBJS += chunk-format.o
> @@ -940,6 +942,7 @@ LIB_OBJS += object.o
>  LIB_OBJS += oid-array.o
>  LIB_OBJS += oidmap.o
>  LIB_OBJS += oidset.o
> +LIB_OBJS += oidtree.o
>  LIB_OBJS += pack-bitmap-write.o
>  LIB_OBJS += pack-bitmap.o
>  LIB_OBJS += pack-check.o
> diff --git a/alloc.c b/alloc.c
> index 957a0af362..ca1e178c5a 100644
> --- a/alloc.c
> +++ b/alloc.c
> @@ -14,6 +14,7 @@
>  #include "tree.h"
>  #include "commit.h"
>  #include "tag.h"
> +#include "oidtree.h"
>  #include "alloc.h"
>
>  #define BLOCKING 1024
> @@ -123,6 +124,11 @@ void *alloc_commit_node(struct repository *r)
>  	return c;
>  }
>
> +void *alloc_from_state(struct alloc_state *alloc_state, size_t n)
> +{
> +	return alloc_node(alloc_state, n);
> +}
> +

Why extend alloc.c instead of using mem-pool.c? (I don't know which fits better, but when you say "memory pool" and not use mem-pool.c I just have to ask..)

Show 52 quoted lines
> diff --git a/oidtree.c b/oidtree.c
> new file mode 100644
> index 0000000000..c1188d8f48
> --- /dev/null
> +++ b/oidtree.c
> @@ -0,0 +1,94 @@
> +/*
> + * A wrapper around cbtree which stores oids
> + * May be used to replace oid-array for prefix (abbreviation) matches
> + */
> +#include "oidtree.h"
> +#include "alloc.h"
> +#include "hash.h"
> +
> +struct oidtree_node {
> +	/* n.k[] is used to store "struct object_id" */
> +	struct cb_node n;
> +};
> +
> +struct oidtree_iter_data {
> +	oidtree_iter fn;
> +	void *arg;
> +	size_t *last_nibble_at;
> +	int algo;
> +	uint8_t last_byte;
> +};
> +
> +void oidtree_destroy(struct oidtree *ot)
> +{
> +	if (ot->mempool) {
> +		clear_alloc_state(ot->mempool);
> +		FREE_AND_NULL(ot->mempool);
> +	}
> +	oidtree_init(ot);
> +}
> +
> +void oidtree_insert(struct oidtree *ot, const struct object_id *oid)
> +{
> +	struct oidtree_node *on;
> +
> +	if (!ot->mempool)
> +		ot->mempool = allocate_alloc_state();
> +	if (!oid->algo)
> +		BUG("oidtree_insert requires oid->algo");
> +
> +	on = alloc_from_state(ot->mempool, sizeof(*on) + sizeof(*oid));
> +	oidcpy_with_padding((struct object_id *)on->n.k, oid);
> +
> +	/*
> +	 * n.b. we shouldn't get duplicates, here, but we'll have
> +	 * a small leak that won't be freed until oidtree_destroy
> +	 */

Why shouldn't we get duplicates? That depends on the usage of oidtree, right? The current user is fine because we avoid reading the same loose object directory twice using the loose_objects_subdir_seen bitmap.

The leak comes from the allocation above, which is not used in case we already have the key in the oidtree. So we need memory for all candidates, not just the inserted candidates. That's probably acceptable in most use cases.

We can do better by keeping track of the unnecessary allocation in struct oidtree and recycling it at the next insert attempt, however. That way we'd only waste at most one slot.

Show 8 quoted lines
> +	cb_insert(&ot->t, &on->n, sizeof(*oid));
> +}
> +
> +int oidtree_contains(struct oidtree *ot, const struct object_id *oid)
> +{
> +	struct object_id k = { 0 };
> +	size_t klen = sizeof(k);
> +	oidcpy_with_padding(&k, oid);

Why initialize k; isn't oidcpy_with_padding() supposed to overwrite it completely?

Show 5 quoted lines
> +
> +	if (oid->algo == GIT_HASH_UNKNOWN) {
> +		k.algo = hash_algo_by_ptr(the_hash_algo);
> +		klen -= sizeof(oid->algo);
> +	}

This relies on the order of the members hash and algo in struct object_id to find a matching hash if we don't actually know algo. It also relies on the absence of padding after algo. Would something like this make sense?

   BUILD_ASSERT_OR_ZERO(offsetof(struct object_id, algo) + sizeof(k.algo) == sizeof(k));

And why set k.algo to some arbitrary value if we ignore it anyway? I.e. why not keep it GIT_HASH_UNKNOWN, as set by oidcpy_with_padding()?

Show 35 quoted lines
> +
> +	return cb_lookup(&ot->t, (const uint8_t *)&k, klen) ? 1 : 0;
> +}
> +
> +static enum cb_next iter(struct cb_node *n, void *arg)
> +{
> +	struct oidtree_iter_data *x = arg;
> +	const struct object_id *oid = (const struct object_id *)n->k;
> +
> +	if (x->algo != GIT_HASH_UNKNOWN && x->algo != oid->algo)
> +		return CB_CONTINUE;
> +
> +	if (x->last_nibble_at) {
> +		if ((oid->hash[*x->last_nibble_at] ^ x->last_byte) & 0xf0)
> +			return CB_CONTINUE;
> +	}
> +
> +	return x->fn(oid, x->arg);
> +}
> +
> +void oidtree_each(struct oidtree *ot, const struct object_id *oid,
> +			size_t oidhexlen, oidtree_iter fn, void *arg)
> +{
> +	size_t klen = oidhexlen / 2;
> +	struct oidtree_iter_data x = { 0 };
> +
> +	x.fn = fn;
> +	x.arg = arg;
> +	x.algo = oid->algo;
> +	if (oidhexlen & 1) {
> +		x.last_byte = oid->hash[klen];
> +		x.last_nibble_at = &klen;
> +	}
> +	cb_each(&ot->t, (const uint8_t *)oid, klen, iter, &x);
> +}
Clamp oidhexlen at GIT_MAX_HEXSZ?  Or die?
René
Previous: Eric WongNext: Eric Wong
Message 31 of 99 in “speed up alt_odb_usable() with many alternates”
  1. speed up alt_odb_usable() with many alternatesEric Wong, Jun 24, 2021
  2. 0/5 optimizations for many odb alternatesEric Wong, Jun 27, 2021
  3. 2/5 avoid strlen via strbuf_addstr in link_alt_odb_entryEric Wong, Jun 27, 2021
  4. 1/5 speed up alt_odb_usable() with many alternatesEric Wong, Jun 27, 2021
  5. 3/5 make object_directory.loose_objects_subdir_seen a bitmapEric Wong, Jun 27, 2021
  6. René ScharfeJun 27, 2021
  7. Eric WongJun 28, 2021
  8. 4/5 oidcpy_with_padding: constify `src' argEric Wong, Jun 27, 2021
  9. 5/5 oidtree: a crit-bit tree for odb_loose_cacheEric Wong, Jun 27, 2021
  10. Junio C HamanoJun 29, 2021
  11. Eric WongJun 29, 2021
  12. 0/5 optimizations for many alternatesEric Wong, Jun 29, 2021
  13. 0/5 optimizations for many alternatesEric Wong, Jul 7, 2021
  14. 1/5 speed up alt_odb_usable() with many alternatesEric Wong, Jul 7, 2021
  15. Junio C HamanoJul 8, 2021
  16. Eric WongJul 8, 2021
  17. Junio C HamanoJul 8, 2021
  18. 2/5 avoid strlen via strbuf_addstr in link_alt_odb_entryEric Wong, Jul 7, 2021
  19. Junio C HamanoJul 8, 2021
  20. 3/5 make object_directory.loose_objects_subdir_seen a bitmapEric Wong, Jul 7, 2021
  21. 4/5 oidcpy_with_padding: constify `src' argEric Wong, Jul 7, 2021
  22. 5/5 oidtree: a crit-bit tree for odb_loose_cacheEric Wong, Jul 7, 2021
  23. 1/5 speed up alt_odb_usable() with many alternatesEric Wong, Jun 29, 2021
  24. René ScharfeJul 3, 2021
  25. René ScharfeJul 4, 2021
  26. Eric WongJul 6, 2021
  27. 2/5 avoid strlen via strbuf_addstr in link_alt_odb_entryEric Wong, Jun 29, 2021
  28. 3/5 make object_directory.loose_objects_subdir_seen a bitmapEric Wong, Jun 29, 2021
  29. 4/5 oidcpy_with_padding: constify `src' argEric Wong, Jun 29, 2021
  30. 5/5 oidtree: a crit-bit tree for odb_loose_cacheEric Wong, Jun 29, 2021
  31. René ScharfeJul 4, 2021
  32. Eric WongJul 6, 2021
  33. Ævar Arnfjörð BjarmasonJul 4, 2021
  34. Eric WongJul 7, 2021
  35. Andrzej HuntAug 6, 2021
  36. René ScharfeAug 6, 2021
  37. Eric WongAug 7, 2021
  38. Carlo ArenasAug 9, 2021
  39. 0/3 pedantic errors in nextCarlo Marcelo Arenas Belón, Aug 9, 2021
  40. 2/3 object-store: avoid extra ';' from KHASH_INITCarlo Marcelo Arenas Belón, Aug 9, 2021
  41. Junio C HamanoAug 9, 2021
  42. 1/3 oidtree: avoid nested struct oidtree_nodeCarlo Marcelo Arenas Belón, Aug 9, 2021
  43. 3/3 ci: run a pedantic build as part of the GitHub workflowCarlo Marcelo Arenas Belón, Aug 9, 2021
  44. Bagas SanjayaAug 9, 2021
  45. Carlo ArenasAug 9, 2021
  46. Phillip WoodAug 9, 2021
  47. Carlo ArenasAug 9, 2021
  48. Phillip WoodAug 10, 2021
  49. Junio C HamanoAug 10, 2021
  50. Ævar Arnfjörð BjarmasonAug 30, 2021
  51. Carlo ArenasAug 31, 2021
  52. Ævar Arnfjörð BjarmasonAug 31, 2021
  53. Carlo ArenasAug 31, 2021
  54. Jeff KingSep 1, 2021
  55. Junio C HamanoSep 1, 2021
  56. Ævar Arnfjörð BjarmasonAug 30, 2021
  57. 0/4 developer: support pedanticCarlo Marcelo Arenas Belón, Sep 1, 2021
  58. 1/4 developer: retire USE_PARENS_AROUND_GETTEXT_N supportCarlo Marcelo Arenas Belón, Sep 1, 2021
  59. 2/4 developer: enable pedantic by defaultCarlo Marcelo Arenas Belón, Sep 1, 2021
  60. 3/4 developer: add an alternative script for detecting broken N_()Carlo Marcelo Arenas Belón, Sep 1, 2021
  61. 4/4 developer: move detect-compiler out of the main directoryCarlo Marcelo Arenas Belón, Sep 1, 2021
  62. Jeff KingSep 1, 2021
  63. gettext: remove optional non-standard parens in N_() definitionÆvar Arnfjörð Bjarmason, Sep 1, 2021
  64. Eric SunshineSep 1, 2021
  65. Jeff KingSep 2, 2021
  66. Junio C HamanoSep 2, 2021
  67. Ævar Arnfjörð BjarmasonSep 1, 2021
  68. Carlo ArenasSep 1, 2021
  69. 0/3 support pedantic in developer modeCarlo Marcelo Arenas Belón, Sep 3, 2021
  70. 1/3 gettext: remove optional non-standard parens in N_() definitionCarlo Marcelo Arenas Belón, Sep 3, 2021
  71. Ævar Arnfjörð BjarmasonSep 10, 2021
  72. 2/3 win32: allow building with pedantic mode enabledCarlo Marcelo Arenas Belón, Sep 3, 2021
  73. René ScharfeSep 3, 2021
  74. Carlo Marcelo Arenas BelónSep 3, 2021
  75. Junio C HamanoSep 3, 2021
  76. René ScharfeSep 3, 2021
  77. René ScharfeSep 4, 2021
  78. Carlo ArenasSep 4, 2021
  79. Jonathan TanSep 27, 2021
  80. Carlo ArenasSep 28, 2021
  81. Jonathan TanSep 28, 2021
  82. Junio C HamanoSep 28, 2021
  83. Jonathan TanSep 28, 2021
  84. Carlo ArenasSep 29, 2021
  85. Junio C HamanoSep 29, 2021
  86. 3/3 developer: enable pedantic by defaultCarlo Marcelo Arenas Belón, Sep 3, 2021
  87. Ævar Arnfjörð BjarmasonSep 5, 2021
  88. Junio C HamanoAug 9, 2021
  89. Eric WongAug 9, 2021
  90. Carlo Marcelo Arenas BelónAug 10, 2021
  91. René ScharfeAug 10, 2021
  92. Carlo ArenasAug 10, 2021
  93. Carlo ArenasAug 11, 2021
  94. René ScharfeAug 11, 2021
  95. Junio C HamanoAug 11, 2021
  96. René ScharfeAug 10, 2021
  97. René ScharfeAug 10, 2021
  98. oidtree: avoid unaligned access to crit-bit treeRené Scharfe, Aug 14, 2021
  99. Junio C HamanoAug 16, 2021

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.