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

Re: [PATCH v5 0/5] Speed up connectivity checks

From
Patrick Steinhardt <ps@pks.im>
Date
Aug 9, 2021, 08:02 UTC
Message-ID
<YRDg+U0IJ3rcst9l@ncase>
In-Reply-To
<cover.1628495467.git.ps@pks.im>

You can ignore this version. I realized I forgot to address some feedback while I was sending mails out. I'll resend in some minutes.

Patrick
On Mon, Aug 09, 2021 at 10:00:39AM +0200, Patrick Steinhardt wrote:
Show 232 quoted lines
> Hi,
> 
> this is the fifth version of my series to speed up connectivity checks
> in the context of repos with many refs. While the original goal has been
> to speed up connectivity checks only, the series is now optimizing
> git-rev-list(1) in general to be able to more efficiently load
> references. Like this, `--not --all` is a lot faster in the context of
> many refs, but other usecases benefit, too.
> 
> Changes compared to v4:
> 
>     - I've changed the interface to load commits via the commit-graph.
>       Instead of the previous version where you'd had to pass in a
>       `struct object`, which forced us to use `lookup_unknown_object()`,
>       one now only passes in an object ID. If the object ID is found in
>       the commit graph and if the corresponding object exists in the
>       ODB, then we return the parsed commit object.
> 
>       This also avoids a previous pitfal: we'd have parsed the commit
>       via the graph and thus had allocated the object even if the
>       corresponding object didn't exist. While we knew to handle this in
>       `get_reference()` by asserting object existence, any other caller
>       which executes `lookup_commit()` would get the parsed commit and
>       assume that it exists. This now cannot happen anymore given that
>       we only create the commit object in case we know the ID exists in
>       the ODB.
> 
>     - With this change, I could now drop the patch which avoided loading
>       objects multiple times: we don't need `lookup_unknown_object()`
>       anymore and thus don't thave the memory/perf tradeoff. And with
>       the new interface to load commits via the graph, the deduplication
>       only resulted in a ~1% speedup.
> 
> Patrick
> 
> Patrick Steinhardt (5):
>   revision: separate walk and unsorted flags
>   connected: do not sort input revisions
>   revision: stop retrieving reference twice
>   commit-graph: split out function to search commit position
>   revision: avoid hitting packfiles when commits are in commit-graph
> 
>  Documentation/rev-list-options.txt |  8 ++-
>  builtin/log.c                      |  2 +-
>  builtin/revert.c                   |  3 +-
>  commit-graph.c                     | 79 ++++++++++++++++++++----------
>  commit-graph.h                     |  8 +++
>  connected.c                        |  1 +
>  revision.c                         | 42 +++++++++-------
>  revision.h                         |  7 +--
>  t/t6000-rev-list-misc.sh           | 38 ++++++++++++++
>  9 files changed, 138 insertions(+), 50 deletions(-)
> 
> Range-diff against v4:
> 1:  67232910ac = 1:  67232910ac revision: separate walk and unsorted flags
> 2:  9d7f484907 = 2:  9d7f484907 connected: do not sort input revisions
> 3:  d8e63d0943 = 3:  d8e63d0943 revision: stop retrieving reference twice
> 4:  ba8df5cad0 < -:  ---------- revision: avoid loading object headers multiple times
> 5:  e33cd51ebf = 4:  549d85e5c2 commit-graph: split out function to search commit position
> 6:  900c5a9c60 ! 5:  4b893d943f revision: avoid hitting packfiles when commits are in commit-graph
>     @@ Metadata
>       ## Commit message ##
>          revision: avoid hitting packfiles when commits are in commit-graph
>      
>     -    When queueing references in git-rev-list(1), we try to either reuse an
>     -    already parsed object or alternatively we load the object header from
>     -    disk in order to determine its type. This is inefficient for commits
>     -    though in cases where we have a commit graph available: instead of
>     -    hitting the real object on disk to determine its type, we may instead
>     -    search the object graph for the object ID. In case it's found, we can
>     -    directly fill in the commit object, otherwise we can still hit the disk
>     -    to determine the object's type.
>     +    When queueing references in git-rev-list(1), we try to optimize parsing
>     +    of commits via the commit-graph. To do so, we first look up the object's
>     +    type, and if it is a commit we call `repo_parse_commit()` instead of
>     +    `parse_object()`. This is quite inefficient though given that we're
>     +    always uncompressing the object header in order to determine the type.
>     +    Instead, we can opportunistically search the commit-graph for the object
>     +    ID: in case it's found, we know it's a commit and can directly fill in
>     +    the commit object without having to uncompress the object header.
>      
>     -    Expose a new function `parse_commit_in_graph_gently()`, which fills in
>     -    an object of unknown type in case we find its object ID in the graph.
>     -    This provides a big performance win in cases where there is a
>     -    commit-graph available in the repository in case we load lots of
>     -    references. The following has been executed in a real-world repository
>     -    with about 2.2 million refs:
>     +    Expose a new function `lookup_commit_in_graph()`, which tries to find a
>     +    commit in the commit-graph by ID, and convert `get_reference()` to use
>     +    this function. This provides a big performance win in cases where we
>     +    load references in a repository with lots of references pointing to
>     +    commits. The following has been executed in a real-world repository with
>     +    about 2.2 million refs:
>      
>              Benchmark #1: HEAD~: rev-list --unsorted-input --objects --quiet --not --all --not $newrev
>     -          Time (mean ± σ):      4.508 s ±  0.039 s    [User: 4.131 s, System: 0.377 s]
>     -          Range (min … max):    4.455 s …  4.576 s    10 runs
>     +          Time (mean ± σ):      4.458 s ±  0.044 s    [User: 4.115 s, System: 0.342 s]
>     +          Range (min … max):    4.409 s …  4.534 s    10 runs
>      
>              Benchmark #2: HEAD: rev-list --unsorted-input --objects --quiet --not --all --not $newrev
>     -          Time (mean ± σ):      3.072 s ±  0.031 s    [User: 2.707 s, System: 0.365 s]
>     -          Range (min … max):    3.040 s …  3.144 s    10 runs
>     +          Time (mean ± σ):      3.089 s ±  0.015 s    [User: 2.768 s, System: 0.321 s]
>     +          Range (min … max):    3.061 s …  3.105 s    10 runs
>      
>              Summary
>                'HEAD: rev-list --unsorted-input --objects --quiet --not --all --not $newrev' ran
>     -            1.47 ± 0.02 times faster than 'HEAD~: rev-list --unsorted-input --objects --quiet --not --all --not $newrev'
>     +            1.44 ± 0.02 times faster than 'HEAD~: rev-list --unsorted-input --objects --quiet --not --all --not $newrev'
>      
>          Signed-off-by: Patrick Steinhardt <ps@pks.im>
>      
>     @@ commit-graph.c: static int find_commit_pos_in_graph(struct commit *item, struct
>       	}
>       }
>       
>     -+int parse_commit_in_graph_gently(struct repository *repo, struct object *object)
>     ++struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id)
>      +{
>      +	struct commit *commit;
>      +	uint32_t pos;
>      +
>     -+	if (object->parsed) {
>     -+		if (object->type != OBJ_COMMIT)
>     -+			return -1;
>     -+		return 0;
>     -+	}
>     -+
>      +	if (!repo->objects->commit_graph)
>     -+		return -1;
>     ++		return NULL;
>     ++	if (!search_commit_pos_in_graph(id, repo->objects->commit_graph, &pos))
>     ++		return NULL;
>     ++	if (!repo_has_object_file(repo, id))
>     ++		return NULL;
>      +
>     -+	if (!search_commit_pos_in_graph(&object->oid, repo->objects->commit_graph, &pos))
>     -+		return -1;
>     -+
>     -+	commit = object_as_type(object, OBJ_COMMIT, 1);
>     ++	commit = lookup_commit(repo, id);
>      +	if (!commit)
>     -+		return -1;
>     -+	if (!fill_commit_in_graph(repo, commit, repo->objects->commit_graph, pos))
>     -+		return -1;
>     ++		return NULL;
>     ++	if (commit->object.parsed)
>     ++		return commit;
>      +
>     -+	return 0;
>     ++	if (!fill_commit_in_graph(repo, commit, repo->objects->commit_graph, pos))
>     ++		return NULL;
>     ++
>     ++	return commit;
>      +}
>      +
>       static int parse_commit_in_graph_one(struct repository *r,
>     @@ commit-graph.h: int open_commit_graph(const char *graph_file, int *fd, struct st
>       int parse_commit_in_graph(struct repository *r, struct commit *item);
>       
>      +/*
>     -+ * Given an object of unknown type, try to fill in the object in case it is a
>     -+ * commit part of the commit-graph. Returns 0 if the object is a parsed commit
>     -+ * or if it could be filled in via the commit graph, otherwise it returns -1.
>     ++ * Look up the given commit ID in the commit-graph. This will only return a
>     ++ * commit if the ID exists both in the graph and in the object database such
>     ++ * that we don't return commits whose object has been pruned. Otherwise, this
>     ++ * function returns `NULL`.
>      + */
>     -+int parse_commit_in_graph_gently(struct repository *repo, struct object *object);
>     ++struct commit *lookup_commit_in_graph(struct repository *repo, const struct object_id *id);
>      +
>       /*
>        * It is possible that we loaded commit contents from the commit buffer,
>     @@ commit-graph.h: int open_commit_graph(const char *graph_file, int *fd, struct st
>      
>       ## revision.c ##
>      @@ revision.c: static struct object *get_reference(struct rev_info *revs, const char *name,
>     - 	struct object *object = lookup_unknown_object(revs->repo, oid);
>     + 				    unsigned int flags)
>     + {
>     + 	struct object *object;
>     ++	struct commit *commit;
>       
>     - 	if (object->type == OBJ_NONE) {
>     --		int type = oid_object_info(revs->repo, oid, NULL);
>     --		if (type < 0 || !object_as_type(object, type, 1)) {
>     -+		/*
>     -+		 * It's likely that the reference points to a commit, so we
>     -+		 * first try to look it up via the commit-graph. If successful,
>     -+		 * then we know it's a commit and don't have to unpack the
>     -+		 * object header. We still need to assert that the object
>     -+		 * exists, but given that we don't request any info about the
>     -+		 * object this is a lot faster than `oid_object_info()`.
>     -+		 */
>     -+		if (parse_commit_in_graph_gently(revs->repo, object) < 0) {
>     -+			int type = oid_object_info(revs->repo, oid, NULL);
>     -+			if (type < 0 || !object_as_type(object, type, 1)) {
>     -+				object = NULL;
>     -+				goto out;
>     -+			}
>     -+		} else if (!repo_has_object_file(revs->repo, oid)) {
>     - 			object = NULL;
>     - 			goto out;
>     - 		}
>     + 	/*
>     +-	 * If the repository has commit graphs, repo_parse_commit() avoids
>     +-	 * reading the object buffer, so use it whenever possible.
>     ++	 * If the repository has commit graphs, we try to opportunistically
>     ++	 * look up the object ID in those graphs. Like this, we can avoid
>     ++	 * parsing commit data from disk.
>     + 	 */
>     +-	if (oid_object_info(revs->repo, oid, NULL) == OBJ_COMMIT) {
>     +-		struct commit *c = lookup_commit(revs->repo, oid);
>     +-		if (!repo_parse_commit(revs->repo, c))
>     +-			object = (struct object *) c;
>     +-		else
>     +-			object = NULL;
>     +-	} else {
>     ++	commit = lookup_commit_in_graph(revs->repo, oid);
>     ++	if (commit)
>     ++		object = &commit->object;
>     ++	else
>     + 		object = parse_object(revs->repo, oid);
>     +-	}
>     + 
>     + 	if (!object) {
>     + 		if (revs->ignore_missing)
> -- 
> 2.32.0
> 
Previous: Patrick SteinhardtNext: Patrick Steinhardt
Message 58 of 64 in “Speed up connectivity checks via bitmaps”
  1. 0/3 Speed up connectivity checks via bitmapsPatrick Steinhardt, Jun 28, 2021
  2. 1/3 p5400: add perf tests for git-receive-pack(1)Patrick Steinhardt, Jun 28, 2021
  3. Ævar Arnfjörð BjarmasonJun 28, 2021
  4. Patrick SteinhardtJun 29, 2021
  5. Ævar Arnfjörð BjarmasonJun 29, 2021
  6. 2/3 receive-pack: skip connectivity checks on delete-only commandsPatrick Steinhardt, Jun 28, 2021
  7. Ævar Arnfjörð BjarmasonJun 28, 2021
  8. Ævar Arnfjörð BjarmasonJun 28, 2021
  9. Patrick SteinhardtJun 29, 2021
  10. Jeff KingJun 30, 2021
  11. Jeff KingJun 30, 2021
  12. Patrick SteinhardtJun 30, 2021
  13. 3/3 connected: implement connectivity check using bitmapsPatrick Steinhardt, Jun 28, 2021
  14. Taylor BlauJun 28, 2021
  15. Taylor BlauJun 29, 2021
  16. Jeff KingJun 30, 2021
  17. Taylor BlauJun 30, 2021
  18. Jeff KingJun 30, 2021
  19. Taylor BlauJul 2, 2021
  20. Jeff KingJul 2, 2021
  21. Jeff KingJun 30, 2021
  22. Patrick SteinhardtJul 20, 2021
  23. 0/4 Speed up connectivity checksPatrick Steinhardt, Aug 2, 2021
  24. 1/4 connected: do not sort input revisionsPatrick Steinhardt, Aug 2, 2021
  25. Ævar Arnfjörð BjarmasonAug 2, 2021
  26. Patrick SteinhardtAug 3, 2021
  27. Ævar Arnfjörð BjarmasonAug 4, 2021
  28. Junio C HamanoAug 2, 2021
  29. Patrick SteinhardtAug 3, 2021
  30. Junio C HamanoAug 3, 2021
  31. 2/4 revision: stop retrieving reference twicePatrick Steinhardt, Aug 2, 2021
  32. Ævar Arnfjörð BjarmasonAug 2, 2021
  33. 3/4 revision: avoid loading object headers multiple timesPatrick Steinhardt, Aug 2, 2021
  34. Ævar Arnfjörð BjarmasonAug 2, 2021
  35. Patrick SteinhardtAug 5, 2021
  36. Junio C HamanoAug 2, 2021
  37. Patrick SteinhardtAug 3, 2021
  38. Patrick SteinhardtAug 6, 2021
  39. 4/4 revision: avoid hitting packfiles when commits are in commit-graphPatrick Steinhardt, Aug 2, 2021
  40. Junio C HamanoAug 2, 2021
  41. Patrick SteinhardtAug 3, 2021
  42. Junio C HamanoAug 3, 2021
  43. Patrick SteinhardtAug 5, 2021
  44. Junio C HamanoAug 5, 2021
  45. Ævar Arnfjörð BjarmasonAug 4, 2021
  46. 0/6 Speed up connectivity checksPatrick Steinhardt, Aug 5, 2021
  47. 1/6 revision: separate walk and unsorted flagsPatrick Steinhardt, Aug 5, 2021
  48. Junio C HamanoAug 5, 2021
  49. 2/6 connected: do not sort input revisionsPatrick Steinhardt, Aug 5, 2021
  50. Junio C HamanoAug 5, 2021
  51. Patrick SteinhardtAug 6, 2021
  52. Junio C HamanoAug 6, 2021
  53. 3/6 revision: stop retrieving reference twicePatrick Steinhardt, Aug 5, 2021
  54. 4/6 revision: avoid loading object headers multiple timesPatrick Steinhardt, Aug 5, 2021
  55. 5/6 commit-graph: split out function to search commit positionPatrick Steinhardt, Aug 5, 2021
  56. 6/6 revision: avoid hitting packfiles when commits are in commit-graphPatrick Steinhardt, Aug 5, 2021
  57. 0/5 Speed up connectivity checksPatrick Steinhardt, Aug 9, 2021
  58. Patrick SteinhardtAug 9, 2021
  59. 0/5 Speed up connectivity checksPatrick Steinhardt, Aug 9, 2021
  60. 1/5 revision: separate walk and unsorted flagsPatrick Steinhardt, Aug 9, 2021
  61. 2/5 connected: do not sort input revisionsPatrick Steinhardt, Aug 9, 2021
  62. 3/5 revision: stop retrieving reference twicePatrick Steinhardt, Aug 9, 2021
  63. 4/5 commit-graph: split out function to search commit positionPatrick Steinhardt, Aug 9, 2021
  64. 5/5 revision: avoid hitting packfiles when commits are in commit-graphPatrick Steinhardt, Aug 9, 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.