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

Re: [PATCH v4 10/19] unpack-trees: add basic support for parallel checkout

From
Matheus Tavares Bernardino <matheus.bernardino@usp.br>
Date
Dec 7, 2020, 19:06 UTC
Message-ID
<CAHd-oW7gn+ryOu+HhNUs6Do6dsamp86hEK3Lz5S7ajXYF1-YSw@mail.gmail.com>
In-Reply-To
<CAP8UFD2+6DYpqM5d1-OCMr4hnGksng4R3e23hcQErT4-ff77=Q@mail.gmail.com>
Hi, Christian

On Sun, Dec 6, 2020 at 8:36 AM Christian Couder <christian.couder@gmail.com> wrote:

Show 11 quoted lines
>
> On Wed, Nov 4, 2020 at 9:34 PM Matheus Tavares
> <matheus.bernardino@usp.br> wrote:
> >
> > This new interface allows us to enqueue some of the entries being
> > checked out to later call write_entry() for them in parallel. For now,
> > the parallel checkout machinery is enabled by default and there is no
> > user configuration, but run_parallel_checkout() just writes the queued
> > entries in sequence (without spawning additional workers). The next
> > patch will actually implement the parallelism and, later, we will make
> > it configurable.

I just noticed that this part of the commit message is a little outdated. I'll fix it for v5. Currently, the parallelism and configuration are added in the same patch (the next one). This way, the patch that adds parallelism can already include runtime numbers for different configuration values (which shows when the change is beneficial).

> I would think that it might be more logical to first add a
> configuration that does nothing, then add writing the queued entries
> in sequence without parallelism, and then add actual parallelism.

I'm not sure I get the idea. Would the first patch add just the documentation for `checkout.workers` and `checkout.thresholdForParallelism` in `Documentation/config/checkout.txt`, without the support for it in the code? In that case, wouldn't the patch become somewhat incomplete on its own?

Show 8 quoted lines
> > When there are path collisions among the entries being written (which
> > can happen e.g. with case-sensitive files in case-insensitive file
> > systems), the parallel checkout code detects the problem and marks the
> > item with PC_ITEM_COLLIDED.
>
> Is this needed in this step that only writes the queued entries in
> sequence without parallelism, or could this be added later, before the
> step that adds actual parallelism?

This is already used in this patch. Even though the parallel checkout machinery only learns to spawn additional workers in the next patch, it can already encounter path collisions when writing the entries sequentially. PC_ITEM_COLLIDED is then used to mark the colliding entries, so that they can be properly handled later.

Show 10 quoted lines
> > Later, these items are sequentially fed to
> > checkout_entry() again. This is similar to the way the sequential code
> > deals with collisions, overwriting the previously checked out entries
> > with the subsequent ones. The only difference is that, when we start
> > writing the entries in parallel, we won't be able to determine which of
> > the colliding entries will survive on disk (for the sequential
> > algorithm, it is always the last one).
>
> So I guess that PC_ITEM_COLLIDED will then be used to decide which
> entries will not be checked out in parallel?

Yes, in the next patch, the parallel workers will detect collisions when `open(path, O_CREAT | O_EXCL)` fails with EEXIST or EISDIR. The workers then mark such items with PC_ITEM_COLLIDED and let the main process sequentially write them later.

Show 8 quoted lines
> > I also experimented with the idea of not overwriting colliding entries,
> > and it seemed to work well in my simple tests.
>
> There are a number of co-author of this patch, so it's not very clear
> who "I" is. Maybe:
>
> "The idea of not overwriting colliding entries seemed to work well in
> simple tests, however ..."
Makes sense, thanks.
Show 28 quoted lines
> > However, because just one
> > entry of each colliding group would be actually written, the others
> > would have null lstat() fields on the index. This might not be a problem
> > by itself, but it could cause performance penalties for subsequent
> > commands that need to refresh the index: when the st_size value cached
> > is 0, read-cache.c:ie_modified() will go to the filesystem to see if the
> > contents match. As mentioned in the function:
> >
> >     * Immediately after read-tree or update-index --cacheinfo,
> >     * the length field is zero, as we have never even read the
> >     * lstat(2) information once, and we cannot trust DATA_CHANGED
> >     * returned by ie_match_stat() which in turn was returned by
> >     * ce_match_stat_basic() to signal that the filesize of the
> >     * blob changed.  We have to actually go to the filesystem to
> >     * see if the contents match, and if so, should answer "unchanged".
> >
> > So, if we have N entries in a colliding group and we decide to write and
> > lstat() only one of them, every subsequent git-status will have to read,
> > convert, and hash the written file N - 1 times, to check that the N - 1
> > unwritten entries are dirty. By checking out all colliding entries (like
> > the sequential code does), we only pay the overhead once.
>
> Ok.
>
> >  5 files changed, 410 insertions(+), 3 deletions(-)
>
> It looks like a lot of new code in one patch/commit, which is why it
> might be interesting to split it.

Yeah, this and the following patch ended up quite big... But I wasn't sure how to further split them while still keeping each part buildable and self-contained :(

Show 24 quoted lines
> > @@ -7,6 +7,7 @@
> >  #include "progress.h"
> >  #include "fsmonitor.h"
> >  #include "entry.h"
> > +#include "parallel-checkout.h"
> >
> >  static void create_directories(const char *path, int path_len,
> >                                const struct checkout *state)
> > @@ -426,8 +427,17 @@ static void mark_colliding_entries(const struct checkout *state,
> >         for (i = 0; i < state->istate->cache_nr; i++) {
> >                 struct cache_entry *dup = state->istate->cache[i];
> >
> > -               if (dup == ce)
> > -                       break;
> > +               if (dup == ce) {
> > +                       /*
> > +                        * Parallel checkout creates the files in no particular
> > +                        * order. So the other side of the collision may appear
> > +                        * after the given cache_entry in the array.
> > +                        */
>
> Is it really the case right now that the code creates files in no
> particular order or will that be the case later when actual
> parallelism is implemented?

In this patch, the code already creates files in no particular order. Since not all entries are eligible for parallel checkout, and because ineligible entries are written first, the files are not created in the same order that they appear in istate->cache[]. (Even though everything is still written sequentially in this patch).

Show 17 quoted lines
> > +                       if (parallel_checkout_status() == PC_RUNNING)
> > +                               continue;
> > +                       else
> > +                               break;
> > +               }
>
> > +struct parallel_checkout_item {
> > +       /* pointer to a istate->cache[] entry. Not owned by us. */
> > +       struct cache_entry *ce;
> > +       struct conv_attrs ca;
> > +       struct stat st;
> > +       enum pc_item_status status;
> > +};
>
> "item" seems not very clear to me. If there is only one
> parallel_checkout_item for each cache_entry then it might be better to
> use "parallel_checkout_entry" instead of "parallel_checkout_item".

Hmm, I'm a little uncertain about this one. I usually use "item" and "entry" interchangeably when talking about elements on a list, as in this case. Could perhaps the 'struct parallel_checkout_item' definition be unclear because it's far from the 'struct parallel_checkout', where the list is actually defined?

Show 16 quoted lines
> > +enum pc_status {
> > +       PC_UNINITIALIZED = 0,
> > +       PC_ACCEPTING_ENTRIES,
> > +       PC_RUNNING,
> > +};
> > +
> > +enum pc_status parallel_checkout_status(void);
> > +void init_parallel_checkout(void);
>
> Maybe a comment to tell what the above function does could be helpful.
> If I had to guess, I would write something like:
>
> /*
>  * Put parallel checkout into the PC_ACCEPTING_ENTRIES state.
>  * Should be used only when in the PC_UNINITIALIZED state.
>  */
OK, will do, thanks!
Show 7 quoted lines
> > +/*
> > + * Return -1 if parallel checkout is currently not enabled
>
> Is it "enabled" or "initialized" or "configured" here? Does it refer
> to `enum pc_status` or a config option or something else? Looking at
> the code, it is testing if the status PC_ACCEPTING_ENTRIES, so
> perhaps: s/not enabled/not accepting entries/
Yes, that's better, thanks!
Previous: Christian CouderNext: Matheus Tavares
Message 59 of 93 in “Parallel Checkout (part I)”
  1. 00/19 Parallel Checkout (part I)Matheus Tavares, Oct 29, 2020
  2. 19/19 ci: run test round with parallel-checkout enabledMatheus Tavares, Oct 29, 2020
  3. 18/19 parallel-checkout: add tests related to .gitattributesMatheus Tavares, Oct 29, 2020
  4. 17/19 parallel-checkout: add tests related to clone collisionsMatheus Tavares, Oct 29, 2020
  5. 16/19 parallel-checkout: add tests for basic operationsMatheus Tavares, Oct 29, 2020
  6. 15/19 checkout-index: add parallel checkout supportMatheus Tavares, Oct 29, 2020
  7. 13/19 make_transient_cache_entry(): optionally alloc from mem_poolMatheus Tavares, Oct 29, 2020
  8. 14/19 builtin/checkout.c: complete parallel checkout supportMatheus Tavares, Oct 29, 2020
  9. 10/19 unpack-trees: add basic support for parallel checkoutMatheus Tavares, Oct 29, 2020
  10. Junio C HamanoNov 2, 2020
  11. Matheus Tavares BernardinoNov 3, 2020
  12. 09/19 entry: add checkout_entry_ca() which takes preloaded conv_attrsMatheus Tavares, Oct 29, 2020
  13. Junio C HamanoOct 30, 2020
  14. 11/19 parallel-checkout: make it truly parallelMatheus Tavares, Oct 29, 2020
  15. 12/19 parallel-checkout: support progress displayingMatheus Tavares, Oct 29, 2020
  16. 08/19 entry: move conv_attrs lookup up to checkout_entry()Matheus Tavares, Oct 29, 2020
  17. Junio C HamanoOct 30, 2020
  18. 05/19 entry: extract a header file for entry.c functionsMatheus Tavares, Oct 29, 2020
  19. Junio C HamanoOct 30, 2020
  20. 07/19 entry: extract cache_entry update from write_entry()Matheus Tavares, Oct 29, 2020
  21. 06/19 entry: make fstat_output() and read_blob_entry() publicMatheus Tavares, Oct 29, 2020
  22. 03/19 convert: add get_stream_filter_ca() variantMatheus Tavares, Oct 29, 2020
  23. Junio C HamanoOct 29, 2020
  24. 04/19 convert: add conv_attrs classificationMatheus Tavares, Oct 29, 2020
  25. Junio C HamanoOct 29, 2020
  26. 02/19 convert: add [async_]convert_to_working_tree_ca() variantsMatheus Tavares, Oct 29, 2020
  27. Junio C HamanoOct 29, 2020
  28. 01/19 convert: make convert_attrs() and convert structs publicMatheus Tavares, Oct 29, 2020
  29. Junio C HamanoOct 29, 2020
  30. Matheus Tavares BernardinoOct 30, 2020
  31. Junio C HamanoOct 30, 2020
  32. Junio C HamanoOct 29, 2020
  33. Jeff HostetlerOct 30, 2020
  34. 00/19 Parallel Checkout (part I)Matheus Tavares, Nov 4, 2020
  35. 01/19 convert: make convert_attrs() and convert structs publicMatheus Tavares, Nov 4, 2020
  36. Christian CouderDec 5, 2020
  37. Matheus Tavares BernardinoDec 5, 2020
  38. 02/19 convert: add [async_]convert_to_working_tree_ca() variantsMatheus Tavares, Nov 4, 2020
  39. Christian CouderDec 5, 2020
  40. Matheus Tavares BernardinoDec 5, 2020
  41. 03/19 convert: add get_stream_filter_ca() variantMatheus Tavares, Nov 4, 2020
  42. Christian CouderDec 5, 2020
  43. 04/19 convert: add conv_attrs classificationMatheus Tavares, Nov 4, 2020
  44. Christian CouderDec 5, 2020
  45. Matheus Tavares BernardinoDec 5, 2020
  46. 05/19 entry: extract a header file for entry.c functionsMatheus Tavares, Nov 4, 2020
  47. Christian CouderDec 6, 2020
  48. 06/19 entry: make fstat_output() and read_blob_entry() publicMatheus Tavares, Nov 4, 2020
  49. 07/19 entry: extract cache_entry update from write_entry()Matheus Tavares, Nov 4, 2020
  50. Christian CouderDec 6, 2020
  51. 08/19 entry: move conv_attrs lookup up to checkout_entry()Matheus Tavares, Nov 4, 2020
  52. Christian CouderDec 6, 2020
  53. Matheus Tavares BernardinoDec 7, 2020
  54. 09/19 entry: add checkout_entry_ca() which takes preloaded conv_attrsMatheus Tavares, Nov 4, 2020
  55. Christian CouderDec 6, 2020
  56. Matheus Tavares BernardinoDec 7, 2020
  57. 10/19 unpack-trees: add basic support for parallel checkoutMatheus Tavares, Nov 4, 2020
  58. Christian CouderDec 6, 2020
  59. Matheus Tavares BernardinoDec 7, 2020
  60. 11/19 parallel-checkout: make it truly parallelMatheus Tavares, Nov 4, 2020
  61. Emily ShafferDec 16, 2020
  62. Matheus Tavares BernardinoDec 17, 2020
  63. 12/19 parallel-checkout: support progress displayingMatheus Tavares, Nov 4, 2020
  64. 13/19 make_transient_cache_entry(): optionally alloc from mem_poolMatheus Tavares, Nov 4, 2020
  65. 15/19 checkout-index: add parallel checkout supportMatheus Tavares, Nov 4, 2020
  66. 17/19 parallel-checkout: add tests related to clone collisionsMatheus Tavares, Nov 4, 2020
  67. 18/19 parallel-checkout: add tests related to .gitattributesMatheus Tavares, Nov 4, 2020
  68. 19/19 ci: run test round with parallel-checkout enabledMatheus Tavares, Nov 4, 2020
  69. 16/19 parallel-checkout: add tests for basic operationsMatheus Tavares, Nov 4, 2020
  70. 14/19 builtin/checkout.c: complete parallel checkout supportMatheus Tavares, Nov 4, 2020
  71. 0/9 Parallel Checkout (part I)Matheus Tavares, Dec 16, 2020
  72. 1/9 convert: make convert_attrs() and convert structs publicMatheus Tavares, Dec 16, 2020
  73. 2/9 convert: add [async_]convert_to_working_tree_ca() variantsMatheus Tavares, Dec 16, 2020
  74. 3/9 convert: add get_stream_filter_ca() variantMatheus Tavares, Dec 16, 2020
  75. 5/9 entry: extract a header file for entry.c functionsMatheus Tavares, Dec 16, 2020
  76. 6/9 entry: make fstat_output() and read_blob_entry() publicMatheus Tavares, Dec 16, 2020
  77. 4/9 convert: add classification for conv_attrs structMatheus Tavares, Dec 16, 2020
  78. 7/9 entry: extract update_ce_after_write() from write_entry()Matheus Tavares, Dec 16, 2020
  79. 9/9 entry: add checkout_entry_ca() taking preloaded conv_attrsMatheus Tavares, Dec 16, 2020
  80. 8/9 entry: move conv_attrs lookup up to checkout_entry()Matheus Tavares, Dec 16, 2020
  81. Christian CouderDec 16, 2020
  82. Junio C HamanoDec 17, 2020
  83. 0/9 Parallel Checkout (part 1)Matheus Tavares, Mar 23, 2021
  84. 1/9 convert: make convert_attrs() and convert structs publicMatheus Tavares, Mar 23, 2021
  85. 2/9 convert: add [async_]convert_to_working_tree_ca() variantsMatheus Tavares, Mar 23, 2021
  86. 4/9 convert: add classification for conv_attrs structMatheus Tavares, Mar 23, 2021
  87. 3/9 convert: add get_stream_filter_ca() variantMatheus Tavares, Mar 23, 2021
  88. 6/9 entry: make fstat_output() and read_blob_entry() publicMatheus Tavares, Mar 23, 2021
  89. 8/9 entry: move conv_attrs lookup up to checkout_entry()Matheus Tavares, Mar 23, 2021
  90. 5/9 entry: extract a header file for entry.c functionsMatheus Tavares, Mar 23, 2021
  91. 9/9 entry: add checkout_entry_ca() taking preloaded conv_attrsMatheus Tavares, Mar 23, 2021
  92. 7/9 entry: extract update_ce_after_write() from write_entry()Matheus Tavares, Mar 23, 2021
  93. Junio C HamanoMar 23, 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.