ref-filter: use single strbuf for all output
PATCH 1/1 ref-filter: use single strbuf for all output
From: ZheNing Hu <[email protected]>
When we use git for-each-ref, every ref will call show_ref_array_item() and allocate its own final strbuf and error strbuf. Instead, we can provide two single strbuf:final_buf and error_buf that get reused for each output.
When run it 100 times:
$ git for-each-ref
on git.git :
3.19s user
3.88s system
35% cpu
20.199 total
to:
2.89s user
4.00s system
34% cpu
19.741 total
The performance has been slightly improved.
---
[GSOC] ref-filter: use single strbuf for all output
This patch learned Jeff King's optimization measures in git cat-file(79ed0a5): using a single strbuf for all objects output Instead of allocating a large number of small strbuf for every object.
So ref-filter can learn same thing: use single buffer: final_buf and error_buf for all refs output.
Thanks.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-927%2Fadlternative%2Fref-filter-single-buf-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-927/adlternative/ref-filter-single-buf-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/927
builtin/for-each-ref.c | 4 +++- builtin/tag.c | 4 +++- ref-filter.c | 21 ++++++++++++--------- ref-filter.h | 5 ++++- 4 files changed, 22 insertions(+), 12 deletions(-)
builtin/for-each-ref.c +3 -1
@@ -22,6 +22,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)struct ref_array array;struct ref_filter filter;struct ref_format format =REF_FORMAT_INIT ; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ;struct option opts[] = { OPT_BIT('s', "shell", &format.quote_style,
@@ -81,7 +83,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)if (!maxcount || array.nr < maxcount) maxcount = array.nr;for (i =0 ; i < maxcount; i++) -show_ref_array_item (array.items[i], &format); +show_ref_array_item (array.items[i], &format, &final_buf, &error_buf);ref_array_clear (&array);return 0 ; }
builtin/tag.c +3 -1
@@ -39,6 +39,8 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, struct ref_format *format) {struct ref_array array; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ;char *to_free =NULL ;int i;
@@ -64,7 +66,7 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,ref_array_sort (sorting, &array);for (i =0 ; i < array.nr; i++) -show_ref_array_item (array.items[i], format); +show_ref_array_item (array.items[i], format, &final_buf, &error_buf);ref_array_clear (&array);free (to_free);
ref-filter.c +12 -9
@@ -2436,16 +2436,16 @@ int format_ref_array_item(struct ref_array_item *info, }void show_ref_array_item (struct ref_array_item *info, -const struct ref_format *format) +const struct ref_format *format, +struct strbuf *final_buf, +struct strbuf *error_buf) { -struct strbuf final_buf =STRBUF_INIT ; -struct strbuf error_buf =STRBUF_INIT ; -if (format_ref_array_item (info, format, &final_buf, &error_buf)) -die ("%s" , error_buf.buf); -fwrite (final_buf.buf,1 , final_buf.len, stdout); -strbuf_release (&error_buf); -strbuf_release (&final_buf); +if (format_ref_array_item (info, format, final_buf, error_buf)) +die ("%s" , error_buf->buf); +fwrite (final_buf->buf,1 , final_buf->len, stdout); +strbuf_reset (error_buf); +strbuf_reset (final_buf);putchar ('\n' ); }
@@ -2453,9 +2453,12 @@ void pretty_print_ref(const char *name, const struct object_id *oid, const struct ref_format *format) {struct ref_array_item *ref_item; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ; + ref_item =new_ref_array_item (name, oid); ref_item->kind =ref_kind_from_refname (name); -show_ref_array_item (ref_item, format); +show_ref_array_item (ref_item, format, &final_buf, &error_buf);free_array_item (ref_item); }
ref-filter.h +4 -1
@@ -120,7 +120,10 @@ int format_ref_array_item(struct ref_array_item *info,struct strbuf *final_buf, struct strbuf *error_buf);/* Print the ref using the given format and quote_style */ -void show_ref_array_item (struct ref_array_item *info,const struct ref_format *format); +void show_ref_array_item (struct ref_array_item *info, +const struct ref_format *format, +struct strbuf *final_buf, +struct strbuf *error_buf);/* Parse a single sort specifier and add it to the list */ void parse_ref_sorting (struct ref_sorting **sorting_tail,const char *atom);/* Callback function for parsing the sort option */
base-commit: 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48
Signature · 1 line
gitgitgadget
Quoted 7 lines · ZheNing Hu via GitGitGadget
When we use
git for-each-ref, every ref will callshow_ref_array_item()and allocate its own final strbuf and error strbuf. Instead, we can provide two single strbuf:final_bufanderror_bufthat get reused for each output.[...]Signed-off-by ZheNing Hu <[email protected]>---
Was there a discussion leading up to this change? If so, it may be a good idea to provide a link to it in the mailing list here under the "---" line.
Some comments below...
Quoted 11 lines
builtin/for-each-ref.c +3
-1@@ -22,6 +22,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)struct ref_format format =REF_FORMAT_INIT ; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ;for (i =0 ; i < maxcount; i++) -show_ref_array_item (array.items[i], &format); +show_ref_array_item (array.items[i], &format, &final_buf, &error_buf);ref_array_clear (&array);return 0 ;
Quoted 1 line
}
The call to strbuf_reset() within show_ref_array_item() does not free the memory from the strbufs, so the memory is being leaked. Therefore, at the end of this function, you should have:
strbuf_release(final_buf);
strbuf_release(error_buf);builtin/tag.c static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, · line 46
Quoted 11 lines
builtin/tag.c +3
-1@@ -39,6 +39,8 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,struct ref_array array; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ;for (i =0 ; i < array.nr; i++) -show_ref_array_item (array.items[i], format); +show_ref_array_item (array.items[i], format, &final_buf, &error_buf);ref_array_clear (&array);free (to_free);
Leaking final_buf and error_buf.
Quoted 13 lines
ref-filter.c +3
-5@@ -2436,16 +2436,16 @@ int format_ref_array_item(struct ref_array_item *info,void show_ref_array_item (struct ref_array_item *info, -const struct ref_format *format) +const struct ref_format *format, +struct strbuf *final_buf, +struct strbuf *error_buf) { -struct strbuf final_buf =STRBUF_INIT ; -struct strbuf error_buf =STRBUF_INIT ; -if (format_ref_array_item (info, format, &final_buf, &error_buf)) -die ("%s" , error_buf.buf);
ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2448
Quoted 10 lines
ref-filter.c +5
-3-fwrite (final_buf.buf,1 , final_buf.len, stdout); -strbuf_release (&error_buf); -strbuf_release (&final_buf); +if (format_ref_array_item (info, format, final_buf, error_buf)) +die ("%s" , error_buf->buf); +fwrite (final_buf->buf,1 , final_buf->len, stdout); +strbuf_reset (error_buf); +strbuf_reset (final_buf);putchar ('\n' ); }
A couple comments:
It is especially ugly that error_buf needs to be passed in by the caller since it is only ever used in case of an error, at which point the program will die() anyhow, so it's not on a critical, speed-sensitive path. The initialization with STRBUF_INIT is practically cost-free, so this variable could easily stay local to this function without cost-penalty rather than forcing the caller to pass it in. (This assumes that none of the consumers of error_buf down the line insert into the buffer unnecessarily, which is probably a reasonable assumption.)
It is an unsafe assumption to only call strbuf_reset() at the end of the function. For this to be robust, you can't assume that the caller has given you an empty strbuf. Instead, you must ensure it by calling strbuf_reset() at the start. (It doesn't hurt to also call strbuf_reset() at the end, but that is not critical to correct operation, so could be omitted.)
ref-filter.c void pretty_print_ref(const char *name, const struct object_id *oid, · line 2461
Quoted 11 lines
ref-filter.c +4
-1@@ -2453,9 +2453,12 @@ void pretty_print_ref(const char *name, const struct object_id *oid,struct ref_array_item *ref_item; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ; + ref_item =new_ref_array_item (name, oid); ref_item->kind =ref_kind_from_refname (name); -show_ref_array_item (ref_item, format); +show_ref_array_item (ref_item, format, &final_buf, &error_buf);free_array_item (ref_item); }
Leaking final_buf and error_buf.
ref-filter.h int format_ref_array_item(struct ref_array_item *info, · line 124
Quoted 8 lines
ref-filter.h +4
-1@@ -120,7 +120,10 @@ int format_ref_array_item(struct ref_array_item *info,/* Print the ref using the given format and quote_style */ -void show_ref_array_item (struct ref_array_item *info,const struct ref_format *format); +void show_ref_array_item (struct ref_array_item *info, +const struct ref_format *format, +struct strbuf *final_buf, +struct strbuf *error_buf);
It is not clear to the person reading this what these two new arguments are for or what should be provided, so the comment above the function deserves an update explaining what these arguments are for and how to provide them. This is especially important since this is a public function.
If this function was merely internal to some builtin command, this sort of change for the sake of optimization might not be so bad, but as a public function, it comes across as rather ugly. In general, we don't necessarily want to pollute an otherwise clean API with changes which make the API uglier merely for the sake of tiny optimizations like this (IMHO). The extra burden placed on callers by this change, coupled with the ugliness this introduces into the API, makes the change seem less than desirable.
One way you might be able to mitigate the undesirableness would be to have two versions of this function. For instance:
/* Print the ref using the given format and quote_style */
show_ref_array_item(...);
/* This is like show_ref_array_item() but ... */
show_ref_array_item_optim(...);The comment of the second function would, of course, need to explain that it is similar to show_ref_array_item() but more optimal because <some reasons> and that the caller is responsible for allocating and releasing the strbufs, and that the strbufs are used only for temporary storage, thus the caller should not assume anything about them.
This way, callers which don't invoke show_ref_array_item() in a tight loop don't need to be burdened by the new arguments (and won't have to remember to release them), and callers with a tight loop can take advantage of the optimization with the bit of extra work of having to declare and release the strbufs.
So, having said all that, it's not clear that the ugliness and extra work are worth the gain. However, if it is decided that the change is worthwhile, then the commit message probably should explain cases in which such an optimization will be really beneficial.
Quoted 6 lines · ZheNing Hu via GitGitGadget
From: ZheNing Hu <[email protected]>
When we use
git for-each-ref, every ref will callshow_ref_array_item()and allocate its own final strbuf and error strbuf. Instead, we can provide two single strbuf:final_bufanderror_bufthat get reused for each output.
s/two single strbuf/two buffers/
Quoted 11 lines
When run it 100 times:
$ git for-each-refon git.git :
3.19s user
3.88s system
35% cpu
20.199 total
Quoted 8 lines
to:
2.89s user
4.00s system
34% cpu
19.741 totalThe performance has been slightly improved.
This is a nice amount of time! Perhaps simplify the presentation:
The performance for 'git for-each-ref' on the Git repository itself with X references changes from 3.2 seconds to 2.9 seconds.
Quoted 7 lines
Signed-off-by ZheNing Hu <[email protected]>---
[GSOC] ref-filter: use single strbuf for all outputThis patch learned Jeff King's optimization measures in git cat-file(79ed0a5): using a single strbuf for all objects output Instead of allocating a large number of small strbuf for every object.
This part of the cover letter could be put into the commit message itself. Use the correct formatting, though:
This approach is similar to the one used by 79ed0a5 (cat-file: use a single strbuf for all output, 2018-08-14) to speed up the cat-file builtin.
I found the code change to be correct. Thanks!
-Stolee
Quoted 16 lines · ZheNing Hu via GitGitGadget
When we use
git for-each-ref, every ref will callshow_ref_array_item()and allocate its own final strbuf and error strbuf. Instead, we can provide two single strbuf:final_bufanderror_bufthat get reused for each output.When run it 100 times:
$ git for-each-refon git.git :
3.19s user
3.88s system
35% cpu
20.199 total
Quoted 6 lines
to:
2.89s user
4.00s system
34% cpu
19.741 total
That's a bigger performance improvement than I'd expect from this. I'm having trouble reproducing it here (I get the same time before and after). I also notice that you don't seem to be CPU bound, and we spend most of our time on system CPU (so program startup stuff, not the loop you're optimizing).
I think a more interesting test is timing a single invocation with a large number of refs. If you are planning to do a lot of work on the formatting code, it might be worth adding such a test into t/perf (both to show off results, but also to catch any regressions after we make things faster).
Quoted 3 lines
This patch learned Jeff King's optimization measures in git cat-file(79ed0a5): using a single strbuf for all objects output Instead of allocating a large number of small strbuf for every object.
I do think this is a good direction (for all the reasons laid out in 79ed0a5), though it wasn't actually the part I was most worried about for ref-filter performance. The bigger issue in ref-filter is that each individual atom will generally have its own allocated string, and then we'll format all of the atoms into the final strbuf output. In most cases we could avoid those intermediate copies entirely.
I do think this would be a useful optimization to have in addition, though. As for the patch itself, I looked over the review that Eric gave, and he already said everything I would have. :)
-Peff
Quoted 11 lines · Eric Sunshine <[email protected]> 于2021年4月6日周二 上午1:05写道:
Quoted 7 lines · ZheNing Hu via GitGitGadget
When we use
git for-each-ref, every ref will callshow_ref_array_item()and allocate its own final strbuf and error strbuf. Instead, we can provide two single strbuf:final_bufanderror_bufthat get reused for each output.[...]Signed-off-by ZheNing Hu <[email protected]>---
Quoted 4 lines
Was there a discussion leading up to this change? If so, it may be a good idea to provide a link to it in the mailing list here under the "---" line.
Okay, I will add them in cover-letter next time.
Quoted 19 lines
Some comments below...
Quoted 11 lines
builtin/for-each-ref.c +3
-1@@ -22,6 +22,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)struct ref_format format =REF_FORMAT_INIT ; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ;for (i =0 ; i < maxcount; i++) -show_ref_array_item (array.items[i], &format); +show_ref_array_item (array.items[i], &format, &final_buf, &error_buf);ref_array_clear (&array);return 0 ;Quoted 1 line
}
The call to
strbuf_reset()withinshow_ref_array_item()does not free the memory from the strbufs, so the memory is being leaked. Therefore, at the end of this function, you should have:
Quoted 3 lines
strbuf_release(final_buf);
strbuf_release(error_buf);Thanks, I ignored this point.
Quoted 40 lines
builtin/tag.c static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, · line 46
Quoted 11 lines
builtin/tag.c +3
-1@@ -39,6 +39,8 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,struct ref_array array; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ;for (i =0 ; i < array.nr; i++) -show_ref_array_item (array.items[i], format); +show_ref_array_item (array.items[i], format, &final_buf, &error_buf);ref_array_clear (&array);free (to_free);Leaking
final_bufanderror_buf.Quoted 13 lines
ref-filter.c +3
-5@@ -2436,16 +2436,16 @@ int format_ref_array_item(struct ref_array_item *info,void show_ref_array_item (struct ref_array_item *info, -const struct ref_format *format) +const struct ref_format *format, +struct strbuf *final_buf, +struct strbuf *error_buf) { -struct strbuf final_buf =STRBUF_INIT ; -struct strbuf error_buf =STRBUF_INIT ; -if (format_ref_array_item (info, format, &final_buf, &error_buf)) -die ("%s" , error_buf.buf);ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2448
Quoted 10 lines
ref-filter.c +5
-3-fwrite (final_buf.buf,1 , final_buf.len, stdout); -strbuf_release (&error_buf); -strbuf_release (&final_buf); +if (format_ref_array_item (info, format, final_buf, error_buf)) +die ("%s" , error_buf->buf); +fwrite (final_buf->buf,1 , final_buf->len, stdout); +strbuf_reset (error_buf); +strbuf_reset (final_buf);putchar ('\n' ); }A couple comments:
Quoted 10 lines
It is especially ugly that
error_bufneeds to be passed in by the caller since it is only ever used in case of an error, at which point the program willdie()anyhow, so it's not on a critical, speed-sensitive path. The initialization withSTRBUF_INITis practically cost-free, so this variable could easily stay local to this function without cost-penalty rather than forcing the caller to pass it in. (This assumes that none of the consumers oferror_bufdown the line insert into the buffer unnecessarily, which is probably a reasonable assumption.)
What you said makes sense. The error_buf may not need to be passed as a parameter, because errors are generally less.
Quoted 7 lines
It is an unsafe assumption to only call
strbuf_reset()at the end of the function. For this to be robust, you can't assume that the caller has given you an empty strbuf. Instead, you must ensure it by callingstrbuf_reset()at the start. (It doesn't hurt to also callstrbuf_reset()at the end, but that is not critical to correct operation, so could be omitted.)
Well, indeed, it would be better to use strbuf_reset() first, as it do in cat-file.c.
Quoted 29 lines
ref-filter.c void pretty_print_ref(const char *name, const struct object_id *oid, · line 2461
Quoted 11 lines
ref-filter.c +4
-1@@ -2453,9 +2453,12 @@ void pretty_print_ref(const char *name, const struct object_id *oid,struct ref_array_item *ref_item; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ; + ref_item =new_ref_array_item (name, oid); ref_item->kind =ref_kind_from_refname (name); -show_ref_array_item (ref_item, format); +show_ref_array_item (ref_item, format, &final_buf, &error_buf);free_array_item (ref_item); }Leaking
final_bufanderror_buf.ref-filter.h int format_ref_array_item(struct ref_array_item *info, · line 124
Quoted 8 lines
ref-filter.h +4
-1@@ -120,7 +120,10 @@ int format_ref_array_item(struct ref_array_item *info,/* Print the ref using the given format and quote_style */ -void show_ref_array_item (struct ref_array_item *info,const struct ref_format *format); +void show_ref_array_item (struct ref_array_item *info, +const struct ref_format *format, +struct strbuf *final_buf, +struct strbuf *error_buf);It is not clear to the person reading this what these two new arguments are for or what should be provided, so the comment above the function deserves an update explaining what these arguments are for and how to provide them. This is especially important since this is a public function.
Quoted 9 lines
If this function was merely internal to some builtin command, this sort of change for the sake of optimization might not be so bad, but as a public function, it comes across as rather ugly. In general, we don't necessarily want to pollute an otherwise clean API with changes which make the API uglier merely for the sake of tiny optimizations like this (IMHO). The extra burden placed on callers by this change, coupled with the ugliness this introduces into the API, makes the change seem less than desirable.
Well, for the time being, there are relatively few places whereshow_ref_array_item()
is used in the entire git repository. I may need to pay attention to this later:
the ease of use of public interfaces is also important.
Quoted 8 lines
One way you might be able to mitigate the undesirableness would be to have two versions of this function. For instance:
/* Print the ref using the given format and quote_style */ show_ref_array_item(...); /* This is like show_ref_array_item() but ... */ show_ref_array_item_optim(...);
Quoted 7 lines
The comment of the second function would, of course, need to explain that it is similar to
show_ref_array_item()but more optimal because <some reasons> and that the caller is responsible for allocating and releasing the strbufs, and that the strbufs are used only for temporary storage, thus the caller should not assume anything about them.
Yes, this will ensure that this new public interface will not be misused.
Quoted 6 lines
This way, callers which don't invoke
show_ref_array_item()in a tight loop don't need to be burdened by the new arguments (and won't have to remember to release them), and callers with a tight loop can take advantage of the optimization with the bit of extra work of having to declare and release the strbufs.
For external calls, reasonable release of strbuf does require attention, which is indeed a disadvantage at some time.
Quoted 4 lines
So, having said all that, it's not clear that the ugliness and extra work are worth the gain. However, if it is decided that the change is worthwhile, then the commit message probably should explain cases in which such an optimization will be really beneficial.
I now estimate that the optimization brought here may appear in a more refs git repo like linux.git. I have to experiment first.
Thanks, Eric.
--
ZheNing Hu
Quoted 29 lines · Derrick Stolee <[email protected]> 于2021年4月6日周二 上午5:02写道:
Quoted 6 lines · ZheNing Hu via GitGitGadget
From: ZheNing Hu <[email protected]>
When we use
git for-each-ref, every ref will callshow_ref_array_item()and allocate its own final strbuf and error strbuf. Instead, we can provide two single strbuf:final_bufanderror_bufthat get reused for each output.s/two single strbuf/two buffers/
Quoted 18 lines
When run it 100 times:
$ git for-each-refon git.git :
3.19s user
3.88s system
35% cpu
20.199 totalto:
2.89s user
4.00s system
34% cpu
19.741 total
Quoted 5 lines
Quoted 1 line
The performance has been slightly improved.
This is a nice amount of time! Perhaps simplify the presentation:
Thanks! But now it may still need more experimental examples to prove that this optimization is visible.
Quoted 12 lines
The performance for 'git for-each-ref' on the Git repository itself with X references changes from 3.2 seconds to 2.9 seconds.
Quoted 7 lines
Signed-off-by ZheNing Hu <[email protected]>---
[GSOC] ref-filter: use single strbuf for all outputThis patch learned Jeff King's optimization measures in git cat-file(79ed0a5): using a single strbuf for all objects output Instead of allocating a large number of small strbuf for every object.
Quoted 8 lines
This part of the cover letter could be put into the commit message itself. Use the correct formatting, though:
This approach is similar to the one used by 79ed0a5 (cat-file: use a single strbuf for all output, 2018-08-14) to speed up the cat-file builtin.
OK, I will do it.
Quoted 2 lines
I found the code change to be correct. Thanks!
-Stolee
Thanks.
--
ZheNing Hu
Quoted 32 lines · Jeff King <[email protected]> 于2021年4月6日周二 上午6:17写道:
Quoted 16 lines · ZheNing Hu via GitGitGadget
When we use
git for-each-ref, every ref will callshow_ref_array_item()and allocate its own final strbuf and error strbuf. Instead, we can provide two single strbuf:final_bufanderror_bufthat get reused for each output.When run it 100 times:
$ git for-each-refon git.git :
3.19s user
3.88s system
35% cpu
20.199 totalQuoted 6 lines
to:
2.89s user
4.00s system
34% cpu
19.741 totalThat's a bigger performance improvement than I'd expect from this. I'm having trouble reproducing it here (I get the same time before and after). I also notice that you don't seem to be CPU bound, and we spend most of our time on system CPU (so program startup stuff, not the loop you're optimizing).
Quoted 6 lines
I think a more interesting test is timing a single invocation with a large number of refs. If you are planning to do a lot of work on the formatting code, it might be worth adding such a test into t/perf (both to show off results, but also to catch any regressions after we make things faster).
It makes sense. A lot of refs can be convincing. Just like the number of objects measured in cat-files is large enough.
But this is the first time I use t/perf/* and there is a little problem. It seem like whatever I run single script like sh ./p0007-write-cache.sh or just make or ./run ${HOME}/git -- ./p0002-read-cache.sh , these tests will fail.
Quoted 11 lines
Quoted 3 lines
This patch learned Jeff King's optimization measures in git cat-file(79ed0a5): using a single strbuf for all objects output Instead of allocating a large number of small strbuf for every object.
I do think this is a good direction (for all the reasons laid out in 79ed0a5), though it wasn't actually the part I was most worried about for ref-filter performance. The bigger issue in ref-filter is that each individual atom will generally have its own allocated string, and then we'll format all of the atoms into the final strbuf output. In most cases we could avoid those intermediate copies entirely.
Yes! In ref-filter we set object info in v->s and then append them to current stack buffer, and finally set in final_buf, the copy of the string is expensive. I don’t know if the optimization should start by removing the stack buffer?
Quoted 4 lines
I do think this would be a useful optimization to have in addition, though. As for the patch itself, I looked over the review that Eric gave, and he already said everything I would have. :)
I think it should be optimized, It will reduce the overhead of malloc and free, but it is not obvious enough.
Yes, there is a lot of bad code in my patch.
Quoted 1 line
-Peff
Thanks.
--
ZheNing Hu
Quoted 5 lines · ZheNing Hu <[email protected]> 于2021年4月6日周二 下午5:49写道:
But this is the first time I use
t/perf/*and there is a little problem. It seem like whatever I run single script likesh ./p0007-write-cache.shor justmakeor./run ${HOME}/git -- ./p0002-read-cache.sh, these tests will fail.
It's because I don't have /usr/bin/time, solved after installation. So best have this:
t/perf/perf-lib.sh +4 -0
@@ -152,6 +152,10 @@ immediate=t # Perf tests require GNU time case "$(uname -s)" in Darwin) GTIME="${GTIME:-gtime}";; esac GTIME="${GTIME:-/usr/bin/time}" +if ! test -f "$GTIME" +then + error "command not found: "$GTIME"" +fi
Thanks.
--
ZheNing Hu
Quoted 12 lines · ZheNing Hu
Quoted 5 lines · ZheNing Hu <[email protected]> 于2021年4月6日周二 下午5:49写道:
But this is the first time I use
t/perf/*and there is a little problem. It seem like whatever I run single script likesh ./p0007-write-cache.shor justmakeor./run ${HOME}/git -- ./p0002-read-cache.sh, these tests will fail.It's because I don't have
/usr/bin/time, solved after installation. So best have this:
t/perf/perf-lib.sh immediate=t · line 158
Quoted 7 lines
t/perf/perf-lib.sh +4
-0@@ -152,6 +152,10 @@ immediate=t # Perf tests require GNU time case "$(uname -s)" in Darwin) GTIME="${GTIME:-gtime}";; esac GTIME="${GTIME:-/usr/bin/time}" +if ! test -f "$GTIME" +then + error "command not found: "$GTIME"" +fi
This patch would create problems when we expect to find the value of $GTIME in the $PATH e.g., you can see in the Darwin case it is set to just "gtime", not an absolute path).
I am sympathetic to helping people see what's wrong, but I think in this case we're better off pointing people to using "-v". E.g.:
$ GTIME=pretend-we-do-not-have-gtime ./p0001-rev-list.sh perf 1 - rev-list --all: not ok 1 - rev-list --all # # git rev-list --all >/dev/null #
Uh oh, that wasn't very informative. But how about this:
$ GTIME=pretend-we-do-not-have-gtime ./p0001-rev-list.sh -v [...] perf 1 - rev-list --all: running: git rev-list --all >/dev/null
./p0001-rev-list.sh: 160: pretend-we-do-not-have-gtime: not found not ok 1 - rev-list --all # # git rev-list --all >/dev/null #
which I think makes it reasonably clear.
-Peff
Quoted 51 lines · Jeff King <[email protected]> 于2021年4月6日周二 下午10:00写道:
Quoted 12 lines · ZheNing Hu
Quoted 5 lines · ZheNing Hu <[email protected]> 于2021年4月6日周二 下午5:49写道:
But this is the first time I use
t/perf/*and there is a little problem. It seem like whatever I run single script likesh ./p0007-write-cache.shor justmakeor./run ${HOME}/git -- ./p0002-read-cache.sh, these tests will fail.It's because I don't have
/usr/bin/time, solved after installation. So best have this:t/perf/perf-lib.sh immediate=t · line 158
Quoted 7 lines
t/perf/perf-lib.sh +4
-0@@ -152,6 +152,10 @@ immediate=t # Perf tests require GNU time case "$(uname -s)" in Darwin) GTIME="${GTIME:-gtime}";; esac GTIME="${GTIME:-/usr/bin/time}" +if ! test -f "$GTIME" +then + error "command not found: "$GTIME"" +fiThis patch would create problems when we expect to find the value of $GTIME in the $PATH e.g., you can see in the Darwin case it is set to just "gtime", not an absolute path).
I am sympathetic to helping people see what's wrong, but I think in this case we're better off pointing people to using "-v". E.g.:
$ GTIME=pretend-we-do-not-have-gtime ./p0001-rev-list.sh perf 1 - rev-list --all: not ok 1 - rev-list --all # # git rev-list --all >/dev/null #Uh oh, that wasn't very informative. But how about this:
$ GTIME=pretend-we-do-not-have-gtime ./p0001-rev-list.sh -v [...] perf 1 - rev-list --all: running: git rev-list --all >/dev/null./p0001-rev-list.sh: 160: pretend-we-do-not-have-gtime: not found not ok 1 - rev-list --all # # git rev-list --all >/dev/null #
Quoted 3 lines
which I think makes it reasonably clear.
-Peff
I just make a small suggestion. ;)
You are right, "-v" is enough.
In addition, I found that the performance was basically unchanged after testing. It seems that this optimization is indeed too small, not as practical as in cat-file.
This shows that the performance bottleneck of ref-filter lies elsewhere. E.g. you mentioned "intermediate copies".
--
ZheNing Hu
Am 05.04.21 um 16:01 schrieb ZheNing Hu via GitGitGadget:
Quoted 18 lines
From: ZheNing Hu <[email protected]>
When we use
git for-each-ref, every ref will callshow_ref_array_item()and allocate its own final strbuf and error strbuf. Instead, we can provide two single strbuf:final_bufanderror_bufthat get reused for each output.When run it 100 times:
$ git for-each-refon git.git :
3.19s user
3.88s system
35% cpu
20.199 total
Quoted 8 lines
to:
2.89s user
4.00s system
34% cpu
19.741 totalThe performance has been slightly improved.
I like to use hyperfine (https://github.com/sharkdp/hyperfine) to get more stable benchmark numbers, incl. standard deviation. With three warmup runs I get the following results for running git for-each-ref on Git's own repo with the current master (2e36527f23):
Benchmark #1: ./git for-each-ref
Time (mean ± σ): 18.8 ms ± 0.3 ms [User: 12.7 ms, System: 5.6 ms]
Range (min … max): 18.2 ms … 19.8 ms 148 runsWith your patch on top I get this:
Benchmark #1: ./git for-each-ref
Time (mean ± σ): 18.5 ms ± 0.4 ms [User: 12.3 ms, System: 5.6 ms]
Range (min … max): 17.8 ms … 19.6 ms 147 runsSo there seems to be a slight improvement here, but it is within the noise.
I'm quite surprised how much longer this takes on your machine, however, and (like Peff already mentioned) how much of the total time it spends in system calls. Is an antivirus program or similar interferring? Or some kind of emulator or similar, e.g. Valgrind? Or has it been a long time since you ran "git gc"?
The benchmark certainly depends on the number of local and remote branches in the repo; my copy currently has 4304 according to "git for-each-ref | wc -l".
Quoted 38 lines
Signed-off-by ZheNing Hu <[email protected]>---
[GSOC] ref-filter: use single strbuf for all outputThis patch learned Jeff King's optimization measures in git cat-file(79ed0a5): using a single strbuf for all objects output Instead of allocating a large number of small strbuf for every object.
So ref-filter can learn same thing: use single buffer:
final_bufanderror_buffor all refs output.Thanks.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-927%2Fadlternative%2Fref-filter-single-buf-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-927/adlternative/ref-filter-single-buf-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/927builtin/for-each-ref.c | 4 +++- builtin/tag.c | 4 +++- ref-filter.c | 21 ++++++++++++--------- ref-filter.h | 5 ++++- 4 files changed, 22 insertions(+), 12 deletions(-)builtin/for-each-ref.c +2
-0@@ -22,6 +22,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)struct ref_array array;struct ref_filter filter;struct ref_format format =REF_FORMAT_INIT ; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ;struct option opts[] = { OPT_BIT('s', "shell", &format.quote_style,
builtin/for-each-ref.c int cmd_for_each_ref(int argc, const char **argv, const char *prefix) · line 86
Quoted 5 lines
builtin/for-each-ref.c +1
-1@@ -81,7 +83,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)if (!maxcount || array.nr < maxcount) maxcount = array.nr;for (i =0 ; i < maxcount; i++) -show_ref_array_item (array.items[i], &format); +show_ref_array_item (array.items[i], &format, &final_buf, &error_buf);
This user of show_ref_array_item() calls it in a loop on an array.
Quoted 17 lines
builtin/for-each-ref.c +0
-0ref_array_clear (&array);return 0 ; }builtin/tag.c +2
-0@@ -39,6 +39,8 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, struct ref_format *format) {struct ref_array array; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ;char *to_free =NULL ;int i;
builtin/tag.c static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, · line 69
Quoted 5 lines
builtin/tag.c +1
-1@@ -64,7 +66,7 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,ref_array_sort (sorting, &array);for (i =0 ; i < array.nr; i++) -show_ref_array_item (array.items[i], format); +show_ref_array_item (array.items[i], format, &final_buf, &error_buf);
Dito.
Quoted 33 lines
builtin/tag.c +0
-0ref_array_clear (&array);free (to_free);ref-filter.c +8
-8@@ -2436,16 +2436,16 @@ int format_ref_array_item(struct ref_array_item *info, }void show_ref_array_item (struct ref_array_item *info, -const struct ref_format *format) +const struct ref_format *format, +struct strbuf *final_buf, +struct strbuf *error_buf) { -struct strbuf final_buf =STRBUF_INIT ; -struct strbuf error_buf =STRBUF_INIT ; -if (format_ref_array_item (info, format, &final_buf, &error_buf)) -die ("%s" , error_buf.buf); -fwrite (final_buf.buf,1 , final_buf.len, stdout); -strbuf_release (&error_buf); -strbuf_release (&final_buf); +if (format_ref_array_item (info, format, final_buf, error_buf)) +die ("%s" , error_buf->buf); +fwrite (final_buf->buf,1 , final_buf->len, stdout); +strbuf_reset (error_buf); +strbuf_reset (final_buf);putchar ('\n' ); }
ref-filter.c void pretty_print_ref(const char *name, const struct object_id *oid, · line 2461
Quoted 10 lines
ref-filter.c +4
-1@@ -2453,9 +2453,12 @@ void pretty_print_ref(const char *name, const struct object_id *oid, const struct ref_format *format) {struct ref_array_item *ref_item; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ; + ref_item =new_ref_array_item (name, oid); ref_item->kind =ref_kind_from_refname (name); -show_ref_array_item (ref_item, format); +show_ref_array_item (ref_item, format, &final_buf, &error_buf);
This third and final caller works with a single item; there is no loop.
Quoted 8 lines
ref-filter.c +0
-0free_array_item (ref_item); }ref-filter.h +0
-0
ref-filter.h int format_ref_array_item(struct ref_array_item *info, · line 126
Quoted 8 lines
ref-filter.h +4
-1@@ -120,7 +120,10 @@ int format_ref_array_item(struct ref_array_item *info,struct strbuf *final_buf, struct strbuf *error_buf);/* Print the ref using the given format and quote_style */ -void show_ref_array_item (struct ref_array_item *info,const struct ref_format *format); +void show_ref_array_item (struct ref_array_item *info, +const struct ref_format *format, +struct strbuf *final_buf, +struct strbuf *error_buf);
This bring-your-own-buffer approach pushes responsibilities back to the callers, in exchange for improved performance. The number of users of this interface is low, so that's defensible. But that added effort is also non-trivial -- as you demonstrated by leaking the allocated memory. ;-)
How about offering to do more instead? In particular you could add a count parameter and have show_ref_array_item() handle an array of struct ref_array_item objects. It could reuse the buffers internally to get the same performance benefit, and would free callers from having to iterate loops themselves. Something like:
void show_ref_array_items(struct ref_array_item **info, size_t n, const struct ref_format *format);
Callers that deal with a single element can pass n = 1.
Perhaps the "format" parameter should go first, like with printf.
The double reference in "**info" is a bit ugly, though (array of pointers instead of a simple array of objects). That's dictated by struct ref_array_item containing a flexible array member, which seems to be hard to change.
Quoted 6 lines
ref-filter.h +0
-0/* Parse a single sort specifier and add it to the list */ void parse_ref_sorting (struct ref_sorting **sorting_tail,const char *atom);/* Callback function for parsing the sort option */ base-commit:
2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48
Quoted 27 lines · René Scharfe <[email protected]> 于2021年4月7日周三 上午2:34写道:
Am 05.04.21 um 16:01 schrieb ZheNing Hu via GitGitGadget:
Quoted 25 lines
From: ZheNing Hu <[email protected]>
When we use
git for-each-ref, every ref will callshow_ref_array_item()and allocate its own final strbuf and error strbuf. Instead, we can provide two single strbuf:final_bufanderror_bufthat get reused for each output.When run it 100 times:
$ git for-each-refon git.git :
3.19s user
3.88s system
35% cpu
20.199 totalto:
2.89s user
4.00s system
34% cpu
19.741 total
Quoted 7 lines
Quoted 1 line
The performance has been slightly improved.
I like to use hyperfine (https://github.com/sharkdp/hyperfine) to get more stable benchmark numbers, incl. standard deviation. With three warmup runs I get the following results for running git for-each-ref on Git's own repo with the current master (
2e36527f23):
Yes, hyperfine is really easy to use!
Quoted 4 lines
Benchmark #1: ./git for-each-ref
Time (mean ± σ): 18.8 ms ± 0.3 ms [User: 12.7 ms, System: 5.6 ms] Range (min … max): 18.2 ms … 19.8 ms 148 runs
Quoted 9 lines
With your patch on top I get this:
Benchmark #1: ./git for-each-ref
Time (mean ± σ): 18.5 ms ± 0.4 ms [User: 12.3 ms, System: 5.6 ms] Range (min … max): 17.8 ms … 19.6 ms 147 runsSo there seems to be a slight improvement here, but it is within the noise.
Yeah. I meet same noise when I do such test.
Quoted 6 lines
I'm quite surprised how much longer this takes on your machine, however, and (like Peff already mentioned) how much of the total time it spends in system calls. Is an antivirus program or similar interferring? Or some kind of emulator or similar, e.g. Valgrind? Or has it been a long time since you ran "git gc"?
Yes, I haven't used git gc for a long time.
In addition, when I did the test before, I ran the network proxy software, so there have a bit notice.
Quoted 4 lines
The benchmark certainly depends on the number of local and remote branches in the repo; my copy currently has 4304 according to "git for-each-ref | wc -l".
Yes i understand this point.
But In my git.git, the result of "git for-each-ref | wc -l" is 8716 refs.
Quoted 131 lines
Quoted 38 lines
Signed-off-by ZheNing Hu <[email protected]>---
[GSOC] ref-filter: use single strbuf for all outputThis patch learned Jeff King's optimization measures in git cat-file(79ed0a5): using a single strbuf for all objects output Instead of allocating a large number of small strbuf for every object.
So ref-filter can learn same thing: use single buffer:
final_bufanderror_buffor all refs output.Thanks.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-927%2Fadlternative%2Fref-filter-single-buf-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-927/adlternative/ref-filter-single-buf-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/927builtin/for-each-ref.c | 4 +++- builtin/tag.c | 4 +++- ref-filter.c | 21 ++++++++++++--------- ref-filter.h | 5 ++++- 4 files changed, 22 insertions(+), 12 deletions(-)builtin/for-each-ref.c +2
-0@@ -22,6 +22,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)struct ref_array array;struct ref_filter filter;struct ref_format format =REF_FORMAT_INIT ; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ;struct option opts[] = { OPT_BIT('s', "shell", &format.quote_style,builtin/for-each-ref.c int cmd_for_each_ref(int argc, const char **argv, const char *prefix) · line 86
Quoted 5 lines
builtin/for-each-ref.c +1
-1@@ -81,7 +83,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)if (!maxcount || array.nr < maxcount) maxcount = array.nr;for (i =0 ; i < maxcount; i++) -show_ref_array_item (array.items[i], &format); +show_ref_array_item (array.items[i], &format, &final_buf, &error_buf);This user of
show_ref_array_item()calls it in a loop on an array.Quoted 17 lines
builtin/for-each-ref.c +0
-0ref_array_clear (&array);return 0 ; }builtin/tag.c +2
-0@@ -39,6 +39,8 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, struct ref_format *format) {struct ref_array array; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ;char *to_free =NULL ;int i;builtin/tag.c static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, · line 69
Quoted 5 lines
builtin/tag.c +1
-1@@ -64,7 +66,7 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,ref_array_sort (sorting, &array);for (i =0 ; i < array.nr; i++) -show_ref_array_item (array.items[i], format); +show_ref_array_item (array.items[i], format, &final_buf, &error_buf);Dito.
Quoted 33 lines
builtin/tag.c +0
-0ref_array_clear (&array);free (to_free);ref-filter.c +8
-8@@ -2436,16 +2436,16 @@ int format_ref_array_item(struct ref_array_item *info, }void show_ref_array_item (struct ref_array_item *info, -const struct ref_format *format) +const struct ref_format *format, +struct strbuf *final_buf, +struct strbuf *error_buf) { -struct strbuf final_buf =STRBUF_INIT ; -struct strbuf error_buf =STRBUF_INIT ; -if (format_ref_array_item (info, format, &final_buf, &error_buf)) -die ("%s" , error_buf.buf); -fwrite (final_buf.buf,1 , final_buf.len, stdout); -strbuf_release (&error_buf); -strbuf_release (&final_buf); +if (format_ref_array_item (info, format, final_buf, error_buf)) +die ("%s" , error_buf->buf); +fwrite (final_buf->buf,1 , final_buf->len, stdout); +strbuf_reset (error_buf); +strbuf_reset (final_buf);putchar ('\n' ); }ref-filter.c void pretty_print_ref(const char *name, const struct object_id *oid, · line 2461
Quoted 10 lines
ref-filter.c +4
-1@@ -2453,9 +2453,12 @@ void pretty_print_ref(const char *name, const struct object_id *oid, const struct ref_format *format) {struct ref_array_item *ref_item; +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ; + ref_item =new_ref_array_item (name, oid); ref_item->kind =ref_kind_from_refname (name); -show_ref_array_item (ref_item, format); +show_ref_array_item (ref_item, format, &final_buf, &error_buf);This third and final caller works with a single item; there is no loop.
Quoted 14 lines
ref-filter.c +0
-0free_array_item (ref_item); }ref-filter.h +2
-1@@ -120,7 +120,10 @@ int format_ref_array_item(struct ref_array_item *info,struct strbuf *final_buf, struct strbuf *error_buf);/* Print the ref using the given format and quote_style */ -void show_ref_array_item (struct ref_array_item *info,const struct ref_format *format); +void show_ref_array_item(struct ref_array_item *info, + const struct ref_format *format,
Quoted 9 lines
ref-filter.h int format_ref_array_item(struct ref_array_item *info, · line 126
Quoted 2 lines
ref-filter.h +2
-0+struct strbuf *final_buf, + struct strbuf *error_buf);This bring-your-own-buffer approach pushes responsibilities back to the callers, in exchange for improved performance. The number of users of this interface is low, so that's defensible. But that added effort is also non-trivial -- as you demonstrated by leaking the allocated memory. ;-)
Yes, this may be burden for the function caller.
Quoted 10 lines
How about offering to do more instead? In particular you could add a count parameter and have
show_ref_array_item()handle an array of structref_array_itemobjects. It could reuse the buffers internally to get the same performance benefit, and would free callers from having to iterate loops themselves. Something like:void show_ref_array_items(struct
ref_array_item**info,
size_tn,
const structref_format*format);
Quoted 9 lines
Callers that deal with a single element can pass n = 1.
Perhaps the "format" parameter should go first, like with printf.
The double reference in "**info" is a bit ugly, though (array of pointers instead of a simple array of objects). That's dictated by struct
ref_array_itemcontaining a flexible array member, which seems to be hard to change.
I personally think this idea is great.
In this way, there is no need to pass in two strbuf from the outside.
+void show_ref_array_items(struct ref_array_item **info,
+ const struct ref_format *format,
+ size_t n)
+{
+ struct strbuf final_buf = STRBUF_INIT; + struct strbuf error_buf = STRBUF_INIT; + size_t i;
+
+ for (i = 0; i < n; i++) {
+ if (format_ref_array_item(info[i], format, &final_buf,&error_buf))
+ die("%s", error_buf.buf);
+ fwrite(final_buf.buf, 1, final_buf.len, stdout);
+ strbuf_reset(&error_buf);
+ strbuf_reset(&final_buf);
+ putchar('\n');
+ }
+ strbuf_release(&error_buf);
+ strbuf_release(&final_buf);+}
+
And the result is here(close the network proxy program):
HEAD~ result :
(git)-[heads/master] % hyperfine "./bin-wrappers/git for-each-ref" --warmup=10
Benchmark #1: ./bin-wrappers/git for-each-ref
Time (mean ± σ): 18.7 ms ± 0.4 ms [User: 14.9 ms,
System: 3.9 ms]
Range (min … max): 18.1 ms … 19.8 ms 141 runs
With the new patch :
(git)-[ref-filter-single-buf] % hyperfine "./bin-wrappers/git
for-each-ref" --warmup=10
Benchmark #1: ./bin-wrappers/git for-each-ref
Time (mean ± σ): 18.2 ms ± 0.3 ms [User: 14.1 ms, System:
4.2 ms]
Range (min … max): 17.4 ms … 19.2 ms 140 runs
Seem like it does have some small advantages ;-)
Quoted 7 lines
Quoted 6 lines
ref-filter.h +0
-0/* Parse a single sort specifier and add it to the list */ void parse_ref_sorting (struct ref_sorting **sorting_tail,const char *atom);/* Callback function for parsing the sort option */ base-commit:
2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48
A new iteration will be sent later.
Thanks!
--
ZheNing Hu
PATCH v2 1/1 ref-filter: use single strbuf for all output
In reply to ZheNing Hu via GitGitGadgetFrom: ZheNing Hu <[email protected]>
When we use git for-each-ref, every ref will call show_ref_array_item() and allocate its own final strbuf and error strbuf. Instead, we can reuse these two strbuf for each step ref's output.
The performance for git for-each-ref on the Git repository itself with performance testing tool hyperfine changes from 18.7 ms ± 0.4 ms to 18.2 ms ± 0.3 ms.
This approach is similar to the one used by 79ed0a5 (cat-file: use a single strbuf for all output, 2018-08-14) to speed up the cat-file builtin.
---
[GSOC] ref-filter: use single strbuf for all output
Now git for-each-ref can reuse two buffers for all refs output, the performance is slightly improved.
Now there may be a question : Should the original interface show_ref_array_items be retained?
Thanks.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-927%2Fadlternative%2Fref-filter-single-buf-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-927/adlternative/ref-filter-single-buf-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/927
Range-diff vs v1:
1: bcc3feb4de7c ! 1: 3aed12c4f5a8 [GSOC] ref-filter: use single strbuf for all output
@@ Commit message
When we use git for-each-ref, every ref will call show_ref_array_item() and allocate its own final strbuf
- and error strbuf. Instead, we can provide two single strbuf:
- final_buf and error_buf that get reused for each output.
+ and error strbuf. Instead, we can reuse these two strbuf
+ for each step ref's output.
- When run it 100 times:
+ The performance for git for-each-ref on the Git repository
+ itself with performance testing tool hyperfine changes from
+ 18.7 ms ± 0.4 ms to 18.2 ms ± 0.3 ms.
- $ git for-each-ref
-
- on git.git :
-
- 3.19s user
- 3.88s system
- 35% cpu
- 20.199 total
-
- to:
-
- 2.89s user
- 4.00s system
- 34% cpu
- 19.741 total
-
- The performance has been slightly improved.
+ This approach is similar to the one used by 79ed0a5
+ (cat-file: use a single strbuf for all output, 2018-08-14)
+ to speed up the cat-file builtin.
## builtin/for-each-ref.c ##
-@@ builtin/for-each-ref.c: int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
- struct ref_array array;
- struct ref_filter filter;
- struct ref_format format = REF_FORMAT_INIT;
-+ struct strbuf final_buf = STRBUF_INIT;
-+ struct strbuf error_buf = STRBUF_INIT;
+@@ builtin/for-each-ref.c: static char const * const for_each_ref_usage[] = {
- struct option opts[] = {
- OPT_BIT('s', "shell", &format.quote_style,
+ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
+ {
+- int i;
+ struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
+ int maxcount = 0, icase = 0;
+ struct ref_array array;
@@ builtin/for-each-ref.c: int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
+
if (!maxcount || array.nr < maxcount)
maxcount = array.nr;
- for (i = 0; i < maxcount; i++)
+- for (i = 0; i < maxcount; i++)
- show_ref_array_item(array.items[i], &format);
-+ show_ref_array_item(array.items[i], &format, &final_buf, &error_buf);
++ show_ref_array_items(array.items, &format, maxcount);
ref_array_clear(&array);
return 0;
}
- ## builtin/tag.c ##
-@@ builtin/tag.c: static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
- struct ref_format *format)
- {
- struct ref_array array;
-+ struct strbuf final_buf = STRBUF_INIT;
-+ struct strbuf error_buf = STRBUF_INIT;
- char *to_free = NULL;
- int i;
-
-@@ builtin/tag.c: static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
- ref_array_sort(sorting, &array);
-
- for (i = 0; i < array.nr; i++)
-- show_ref_array_item(array.items[i], format);
-+ show_ref_array_item(array.items[i], format, &final_buf, &error_buf);
- ref_array_clear(&array);
- free(to_free);
-
-
## ref-filter.c ##
@@ ref-filter.c: int format_ref_array_item(struct ref_array_item *info,
+ return 0;
}
- void show_ref_array_item(struct ref_array_item *info,
-- const struct ref_format *format)
++void show_ref_array_items(struct ref_array_item **info,
+ const struct ref_format *format,
-+ struct strbuf *final_buf,
-+ struct strbuf *error_buf)
- {
-- struct strbuf final_buf = STRBUF_INIT;
-- struct strbuf error_buf = STRBUF_INIT;
-
-- if (format_ref_array_item(info, format, &final_buf, &error_buf))
-- die("%s", error_buf.buf);
-- fwrite(final_buf.buf, 1, final_buf.len, stdout);
-- strbuf_release(&error_buf);
-- strbuf_release(&final_buf);
-+ if (format_ref_array_item(info, format, final_buf, error_buf))
-+ die("%s", error_buf->buf);
-+ fwrite(final_buf->buf, 1, final_buf->len, stdout);
-+ strbuf_reset(error_buf);
-+ strbuf_reset(final_buf);
- putchar('\n');
- }
-
-@@ ref-filter.c: void pretty_print_ref(const char *name, const struct object_id *oid,
- const struct ref_format *format)
- {
- struct ref_array_item *ref_item;
++ size_t n)
++{
+ struct strbuf final_buf = STRBUF_INIT;
+ struct strbuf error_buf = STRBUF_INIT;
++ size_t i;
+
- ref_item = new_ref_array_item(name, oid);
- ref_item->kind = ref_kind_from_refname(name);
-- show_ref_array_item(ref_item, format);
-+ show_ref_array_item(ref_item, format, &final_buf, &error_buf);
- free_array_item(ref_item);
- }
-
++ for (i = 0; i < n; i++) {
++ if (format_ref_array_item(info[i], format, &final_buf, &error_buf))
++ die("%s", error_buf.buf);
++ fwrite(final_buf.buf, 1, final_buf.len, stdout);
++ strbuf_reset(&error_buf);
++ strbuf_reset(&final_buf);
++ putchar('\n');
++ }
++ strbuf_release(&error_buf);
++ strbuf_release(&final_buf);
++}
++
+ void show_ref_array_item(struct ref_array_item *info,
+ const struct ref_format *format)
+ {
## ref-filter.h ##
@@ ref-filter.h: int format_ref_array_item(struct ref_array_item *info,
- struct strbuf *final_buf,
struct strbuf *error_buf);
/* Print the ref using the given format and quote_style */
--void show_ref_array_item(struct ref_array_item *info, const struct ref_format *format); -+void show_ref_array_item(struct ref_array_item *info,
+ void show_ref_array_item(struct ref_array_item *info, const struct ref_format *format); ++/* Print the refs using the given format and quote_style and maxcount */ ++void show_ref_array_items(struct ref_array_item **info,
+ const struct ref_format *format,
-+ struct strbuf *final_buf,
-+ struct strbuf *error_buf);
++ size_t n);
++
/* Parse a single sort specifier and add it to the list */
void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *atom); /* Callback function for parsing the sort option */
builtin/for-each-ref.c | 4 +--- ref-filter.c | 20 ++++++++++++++++++++ ref-filter.h | 5 +++++ 3 files changed, 26 insertions(+), 3 deletions(-)
builtin/for-each-ref.c +1 -3
@@ -16,7 +16,6 @@ static char const * const for_each_ref_usage[] = {int cmd_for_each_ref (int argc,const char **argv,const char *prefix) { -int i;struct ref_sorting *sorting =NULL , **sorting_tail = &sorting;int maxcount =0 , icase =0 ;struct ref_array array;
@@ -80,8 +79,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)if (!maxcount || array.nr < maxcount) maxcount = array.nr; -for (i =0 ; i < maxcount; i++) -show_ref_array_item (array.items[i], &format); +show_ref_array_items (array.items, &format, maxcount);ref_array_clear (&array);return 0 ; }
ref-filter.c +20 -0
@@ -2435,6 +2435,26 @@ int format_ref_array_item(struct ref_array_item *info,return 0 ; } +void show_ref_array_items (struct ref_array_item **info, +const struct ref_format *format, +size_t n) +{ +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ; +size_t i; + +for (i =0 ; i < n; i++) { +if (format_ref_array_item (info[i], format, &final_buf, &error_buf)) +die ("%s" , error_buf.buf); +fwrite (final_buf.buf,1 , final_buf.len, stdout); +strbuf_reset (&error_buf); +strbuf_reset (&final_buf); +putchar ('\n' ); + } +strbuf_release (&error_buf); +strbuf_release (&final_buf); +} +void show_ref_array_item (struct ref_array_item *info,const struct ref_format *format) {
ref-filter.h +5 -0
@@ -121,6 +121,11 @@ int format_ref_array_item(struct ref_array_item *info, struct strbuf *error_buf);/* Print the ref using the given format and quote_style */ void show_ref_array_item (struct ref_array_item *info,const struct ref_format *format); +/* Print the refs using the given format and quote_style and maxcount */ +void show_ref_array_items (struct ref_array_item **info, +const struct ref_format *format, +size_t n); +/* Parse a single sort specifier and add it to the list */ void parse_ref_sorting (struct ref_sorting **sorting_tail,const char *atom);/* Callback function for parsing the sort option */
base-commit: 2e36527f23b7f6ae15e6f21ac3b08bf3fed6ee48
Signature · 1 line
gitgitgadget
Quoted 1 line · ZheNing Hu via GitGitGadget
Subject: Re: [PATCH v2] [GSOC] ref-filter: use single strbuf for all output
The implementation changed so much from the initial attempt, for which the above title may have been appropriate, that reusing single strbuf over and over is not the most important part of the change anymore, I am afraid. Besides, it uses TWO strbufs ;-)
Subject: [PATCH] ref-filter: introduce show_ref_array_items() helper
or something like that?
Quoted 19 lines
From: ZheNing Hu <[email protected]>
When we use
git for-each-ref, every ref will callshow_ref_array_item()and allocate its own final strbuf and error strbuf. Instead, we can reuse these two strbuf for each step ref's output.The performance for
git for-each-refon the Git repository itself with performance testing toolhyperfinechanges from 18.7 ms ± 0.4 ms to 18.2 ms ± 0.3 ms.This approach is similar to the one used by 79ed0a5 (cat-file: use a single strbuf for all output, 2018-08-14) to speed up the cat-file builtin.
Signed-off-by ZheNing Hu <[email protected]>---
[GSOC] ref-filter: use single strbuf for all output
Quoted 7 lines
Now git for-each-ref can reuse two buffers for all refs output, the performance is slightly improved.
Now there may be a question : Should the original interface
show_ref_array_itemsbe retained?
.../* Callback function for parsing the sort option */
Again, not a very useful range-diff as the implementation changed so much.
Quoted 18 lines
builtin/for-each-ref.c | 4 +--- ref-filter.c | 20 ++++++++++++++++++++ ref-filter.h | 5 +++++ 3 files changed, 26 insertions(+), 3 deletions(-)builtin/for-each-ref.c +0
-1@@ -16,7 +16,6 @@ static char const * const for_each_ref_usage[] = {int cmd_for_each_ref (int argc,const char **argv,const char *prefix) { -int i;struct ref_sorting *sorting =NULL , **sorting_tail = &sorting;int maxcount =0 , icase =0 ;struct ref_array array;
builtin/for-each-ref.c int cmd_for_each_ref(int argc, const char **argv, const char *prefix) · line 82
Quoted 6 lines
builtin/for-each-ref.c +1
-2@@ -80,8 +79,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)if (!maxcount || array.nr < maxcount) maxcount = array.nr; -for (i =0 ; i < maxcount; i++) -show_ref_array_item (array.items[i], &format); +show_ref_array_items (array.items, &format, maxcount);
The intention of this call is to pass an array and the number of elements in the array as a pair to the function, right? When you design the API for a new helper function, do not split them apart by inserting an unrelated parameter in the middle.
ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2440
Quoted 11 lines
ref-filter.c +3
-0@@ -2435,6 +2435,26 @@ int format_ref_array_item(struct ref_array_item *info,return 0 ; } +void show_ref_array_items (struct ref_array_item **info, +const struct ref_format *format, +size_t n)
IOW,
void show_ref_array_items(const struct ref_format *format, struct ref_array_item *info[], size_t n)
ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2448
Quoted 8 lines
ref-filter.c +8
-0+{ +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ; +size_t i; + +for (i =0 ; i < n; i++) { +if (format_ref_array_item (info[i], format, &final_buf, &error_buf)) +die ("%s" , error_buf.buf);
OK, the contents of error_buf is already localized, so it is correct not to have _() around the "%s" here.
ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2452
Quoted 4 lines
ref-filter.c +4
-0+fwrite (final_buf.buf,1 , final_buf.len, stdout); +strbuf_reset (&error_buf); +strbuf_reset (&final_buf); +putchar ('\n' );
This is inherited code, but splitting fwrite() and putchar() apart like this makes the code hard to follow. Perhaps clean it up later when nothing else is going on in the code as leftoverbits, outside the topic.
ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2460
Quoted 8 lines
ref-filter.c +5
-0+ } +strbuf_release (&error_buf); +strbuf_release (&final_buf); +} +void show_ref_array_item (struct ref_array_item *info,const struct ref_format *format) {
Isn't the point of the new helper function so that this can become a thin wrapper around it, i.e.
void show_ref_array_item(...)
{
show_ref_array_items(format, &info, 1);
}ref-filter.h int format_ref_array_item(struct ref_array_item *info, · line 127
Quoted 12 lines
ref-filter.h +4
-0@@ -121,6 +121,11 @@ int format_ref_array_item(struct ref_array_item *info, struct strbuf *error_buf);/* Print the ref using the given format and quote_style */ void show_ref_array_item (struct ref_array_item *info,const struct ref_format *format); +/* Print the refs using the given format and quote_style and maxcount */ +void show_ref_array_items (struct ref_array_item **info, +const struct ref_format *format, +size_t n);
The inconsistency between "maxcount" vs "n" is irritating. Calling the parameter with a name that has the word "info" (because the new parameter is about that array) and a word like "nelem" to hint that it is the number of elements in the array) would be sensible.
void show_ref_array_items(const struct ref_format *format,
struct ref_array_item *info[], size_t info_count);
or something along the line, perhaps?
Quoted 17 lines · ZheNing Hu via GitGitGadget
ref-filter.c +9
-0@@ -2435,6 +2435,26 @@ int format_ref_array_item(struct ref_array_item *info,return 0 ; } +void show_ref_array_items (struct ref_array_item **info, +const struct ref_format *format, +size_t n) +{ +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ; +size_t i; + +for (i =0 ; i < n; i++) {
ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2456
Quoted 10 lines
ref-filter.c +10
-0+if (format_ref_array_item (info[i], format, &final_buf, &error_buf)) +die ("%s" , error_buf.buf); +fwrite (final_buf.buf,1 , final_buf.len, stdout); +strbuf_reset (&error_buf); +strbuf_reset (&final_buf); +putchar ('\n' ); + } +strbuf_release (&error_buf); +strbuf_release (&final_buf); +}
I think this is a reasonable direction to take the solution: wrapping the loop so that the reuse of the buffers can be included there.
But I do wonder if we should go the opposite direction, and get rid of show_ref_array_item() entirely. It only has two callers, both of which could just write the loop themselves. That is more code, but perhaps it would make it more clear what is going on in those callers, and to give them more flexibility.
I notice there's a third user of the ref-filter.c code in builtin/branch.c that does not use show_ref_array_item(). Instead, it loops itself and calls format_ref_array_item(). I think this is because it is sometimes columnizing the results. Perhaps git-tag and for-each-ref would want to learn the same trick, in which case they'd be happy to have the open-coded loop.
Either way, it probably makes sense to introduce the same optimization to the case in builtin/branch.c.
-Peff
Quoted 5 lines · Junio C Hamano <[email protected]> 于2021年4月8日周四 上午4:31写道:
Quoted 1 line · ZheNing Hu via GitGitGadget
Subject: Re: [PATCH v2] [GSOC] ref-filter: use single strbuf for all output
Quoted 9 lines
The implementation changed so much from the initial attempt, for which the above title may have been appropriate, that reusing single strbuf over and over is not the most important part of the change anymore, I am afraid. Besides, it uses TWO strbufs ;-)
Subject: [PATCH] ref-filter: introduce
show_ref_array_items()helperor something like that?
Yep, I may think that its core is still reusing strbufs, but "introduce show_ref_array_items()" will be more accurate.
Quoted 22 lines
Quoted 22 lines
From: ZheNing Hu <[email protected]>
When we use
git for-each-ref, every ref will callshow_ref_array_item()and allocate its own final strbuf and error strbuf. Instead, we can reuse these two strbuf for each step ref's output.The performance for
git for-each-refon the Git repository itself with performance testing toolhyperfinechanges from 18.7 ms ± 0.4 ms to 18.2 ms ± 0.3 ms.This approach is similar to the one used by 79ed0a5 (cat-file: use a single strbuf for all output, 2018-08-14) to speed up the cat-file builtin.
Signed-off-by ZheNing Hu <[email protected]>---
[GSOC] ref-filter: use single strbuf for all outputNow git for-each-ref can reuse two buffers for all refs output, the performance is slightly improved.
Quoted 7 lines
Quoted 4 lines
Now there may be a question : Should the original interface
show_ref_array_itemsbe retained?
.../* Callback function for parsing the sort option */Again, not a very useful range-diff as the implementation changed so much.
This makes me wonder if I should give up GGG in the future. I also don’t want a rang-diff with a big difference.
Quoted 20 lines
Quoted 20 lines
builtin/for-each-ref.c | 4 +--- ref-filter.c | 20 ++++++++++++++++++++ ref-filter.h | 5 +++++ 3 files changed, 26 insertions(+), 3 deletions(-)builtin/for-each-ref.c +0
-1@@ -16,7 +16,6 @@ static char const * const for_each_ref_usage[] = {int cmd_for_each_ref (int argc,const char **argv,const char *prefix) { -int i;struct ref_sorting *sorting =NULL , **sorting_tail = &sorting;int maxcount =0 , icase =0 ;struct ref_array array;@@ -80,8 +79,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
Quoted 11 lines
builtin/for-each-ref.c int cmd_for_each_ref(int argc, const char **argv, const char *prefix) · line 82
Quoted 5 lines
builtin/for-each-ref.c +1
-2if (!maxcount || array.nr < maxcount) maxcount = array.nr; -for (i =0 ; i < maxcount; i++) -show_ref_array_item (array.items[i], &format); +show_ref_array_items (array.items, &format, maxcount);The intention of this call is to pass an array and the number of elements in the array as a pair to the function, right? When you design the API for a new helper function, do not split them apart by inserting an unrelated parameter in the middle.
Eh, are you saying that maxcount is irrelevant here? There should be maxcount, because we need to limit the number of iterations here.
Quoted 8 lines
Quoted 8 lines
ref-filter.c +0
-0@@ -2435,6 +2435,26 @@ int format_ref_array_item(struct ref_array_item *info,return 0 ; }
Quoted 9 lines
ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2440
Quoted 3 lines
ref-filter.c +3
-0+void show_ref_array_items (struct ref_array_item **info, +const struct ref_format *format, +size_t n)IOW,
void show_ref_array_items(const struct
ref_format*format,
structref_array_item*info[],size_tn)
Yes, it will be more obvious in the form of an array.
Quoted 17 lines
ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2448
Quoted 8 lines
ref-filter.c +8
-0+{ +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ; +size_t i; + +for (i =0 ; i < n; i++) { +if (format_ref_array_item (info[i], format, &final_buf, &error_buf)) +die ("%s" , error_buf.buf);OK, the contents of
error_bufis already localized, so it is correct not to have_()around the "%s" here.ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2452
Quoted 4 lines
ref-filter.c +4
-0+fwrite (final_buf.buf,1 , final_buf.len, stdout); +strbuf_reset (&error_buf); +strbuf_reset (&final_buf); +putchar ('\n' );
Quoted 5 lines
This is inherited code, but splitting
fwrite()andputchar()apart like this makes the code hard to follow. Perhaps clean it up later when nothing else is going on in the code as leftoverbits, outside the topic.
Ok, swap the position of reset and putchar.
Quoted 12 lines
ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2460
Quoted 8 lines
ref-filter.c +5
-0+ } +strbuf_release (&error_buf); +strbuf_release (&final_buf); +} +void show_ref_array_item (struct ref_array_item *info,const struct ref_format *format) {Isn't the point of the new helper function so that this can become a thin wrapper around it, i.e.
Quoted 5 lines
void show_ref_array_item(...)
{
show_ref_array_items(format, &info, 1);
}Maybe it makes sense. But as Peff said, Maybe we can just delete it.
Quoted 18 lines
ref-filter.h int format_ref_array_item(struct ref_array_item *info, · line 127
Quoted 12 lines
ref-filter.h +4
-0@@ -121,6 +121,11 @@ int format_ref_array_item(struct ref_array_item *info, struct strbuf *error_buf);/* Print the ref using the given format and quote_style */ void show_ref_array_item (struct ref_array_item *info,const struct ref_format *format); +/* Print the refs using the given format and quote_style and maxcount */ +void show_ref_array_items (struct ref_array_item **info, +const struct ref_format *format, +size_t n);The inconsistency between "maxcount" vs "n" is irritating. Calling the parameter with a name that has the word "info" (because the new parameter is about that array) and a word like "nelem" to hint that it is the number of elements in the array) would be sensible.
Quoted 5 lines
void show_ref_array_items(const struct
ref_format*format,struct ref_array_item *info[], size_t info_count);or something along the line, perhaps?
Aha, I guess this is the reason for the misunderstanding above. Yes, info_count is the correct meaning and the meaning of n is wrong.
Thanks.
--
ZheNing Hu
Quoted 34 lines · Jeff King <[email protected]> 于2021年4月8日周四 上午5:27写道:
Quoted 17 lines · ZheNing Hu via GitGitGadget
ref-filter.c +9
-0@@ -2435,6 +2435,26 @@ int format_ref_array_item(struct ref_array_item *info,return 0 ; } +void show_ref_array_items (struct ref_array_item **info, +const struct ref_format *format, +size_t n) +{ +struct strbuf final_buf =STRBUF_INIT ; +struct strbuf error_buf =STRBUF_INIT ; +size_t i; + +for (i =0 ; i < n; i++) {ref-filter.c int format_ref_array_item(struct ref_array_item *info, · line 2456
Quoted 10 lines
ref-filter.c +10
-0+if (format_ref_array_item (info[i], format, &final_buf, &error_buf)) +die ("%s" , error_buf.buf); +fwrite (final_buf.buf,1 , final_buf.len, stdout); +strbuf_reset (&error_buf); +strbuf_reset (&final_buf); +putchar ('\n' ); + } +strbuf_release (&error_buf); +strbuf_release (&final_buf); +}I think this is a reasonable direction to take the solution: wrapping the loop so that the reuse of the buffers can be included there.
Quoted 6 lines
But I do wonder if we should go the opposite direction, and get rid of
show_ref_array_item()entirely. It only has two callers, both of which could just write the loop themselves. That is more code, but perhaps it would make it more clear what is going on in those callers, and to give them more flexibility.
Indeed. I think pretty_print_ref() is proof that we may need to keep show_ref_array_item() because If it modified to show_ref_array_items(...,1); it will look very strange.
Quoted 7 lines
I notice there's a third user of the
ref-filter.ccode inbuiltin/branch.cthat does not useshow_ref_array_item(). Instead, it loops itself and callsformat_ref_array_item(). I think this is because it is sometimes columnizing the results. Perhaps git-tag and for-each-ref would want to learn the same trick, in which case they'd be happy to have the open-coded loop.
Yes, a special judgment about colopts is used in the print_ref_list() of branch.c.
Quoted 3 lines
Either way, it probably makes sense to introduce the same optimization to the case in
builtin/branch.c.
I agree in branch.c here can learn this reusing bufs optimization.
Quoted 1 line
-Peff
Thanks.
--
ZheNing Hu
Quoted 10 lines · ZheNing Hu
Quoted 9 lines
I think this is a reasonable direction to take the solution: wrapping the loop so that the reuse of the buffers can be included there.
But I do wonder if we should go the opposite direction, and get rid of
show_ref_array_item()entirely. It only has two callers, both of which could just write the loop themselves. That is more code, but perhaps it would make it more clear what is going on in those callers, and to give them more flexibility.
Quoted 3 lines
Indeed. I think
pretty_print_ref()is proof that we may need to keepshow_ref_array_item()because If it modified toshow_ref_array_items(...,1);it will look very strange.
What I meant was that we should get rid of show_ref_array_items(), as well, and just use format_ref_array_item() everywhere. This whole wrapper is only saving us a few lines, and it makes it harder to see what the function is doing. Likewise for pretty-print ref. But I dunno. Maybe that is all going too far.
-Peff
Quoted 17 lines · Jeff King <[email protected]> 于2021年4月8日周四 下午10:32写道:
Quoted 10 lines · ZheNing Hu
Quoted 9 lines
I think this is a reasonable direction to take the solution: wrapping the loop so that the reuse of the buffers can be included there.
But I do wonder if we should go the opposite direction, and get rid of
show_ref_array_item()entirely. It only has two callers, both of which could just write the loop themselves. That is more code, but perhaps it would make it more clear what is going on in those callers, and to give them more flexibility.Quoted 3 lines
Indeed. I think
pretty_print_ref()is proof that we may need to keepshow_ref_array_item()because If it modified toshow_ref_array_items(...,1);it will look very strange.
Quoted 6 lines
What I meant was that we should get rid of
show_ref_array_items(), as well, and just useformat_ref_array_item()everywhere. This whole wrapper is only saving us a few lines, and it makes it harder to see what the function is doing. Likewise for pretty-print ref. But I dunno. Maybe that is all going too far.
Ok... so you mean we just use a loop like in branch.c, and get rid of show_ref_array_items() and show_ref_array_item().
(We can still use the optimization of reuse bufs)
Quoted 1 line
-Peff
--
ZheNing Hu
Quoted 10 lines · ZheNing Hu
Quoted 6 lines
What I meant was that we should get rid of
show_ref_array_items(), as well, and just useformat_ref_array_item()everywhere. This whole wrapper is only saving us a few lines, and it makes it harder to see what the function is doing. Likewise for pretty-print ref. But I dunno. Maybe that is all going too far.Ok... so you mean we just use a loop like in
branch.c, and get rid ofshow_ref_array_items()andshow_ref_array_item().
(We can still use the optimization of reuse bufs)
Yes, something like this:
builtin/for-each-ref.c +12 -2
@@ -22,6 +22,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)struct ref_array array;struct ref_filter filter;struct ref_format format =REF_FORMAT_INIT ; +struct strbuf output =STRBUF_INIT ; +struct strbuf err =STRBUF_INIT ;struct option opts[] = { OPT_BIT('s', "shell", &format.quote_style,
@@ -80,8 +82,16 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)if (!maxcount || array.nr < maxcount) maxcount = array.nr; -for (i =0 ; i < maxcount; i++) -show_ref_array_item (array.items[i], &format); + +for (i =0 ; i < maxcount; i++) { +strbuf_reset (&output); +if (format_ref_array_item (array.items[i], &format, &output, &err)) +die ("%s" , err.buf); +fwrite (output.buf,1 , output.len, stdout); +putchar ('\n' ); + } + +strbuf_release (&output);ref_array_clear (&array);return 0 ; }
It is dropping a few lines by assuming that the error buf is only touched when we return an error (which IMHO is a reasonable assumption to make).
-Peff
Quoted 49 lines · Jeff King <[email protected]> 于2021年4月8日周四 下午10:51写道:
Quoted 10 lines · ZheNing Hu
Quoted 6 lines
What I meant was that we should get rid of
show_ref_array_items(), as well, and just useformat_ref_array_item()everywhere. This whole wrapper is only saving us a few lines, and it makes it harder to see what the function is doing. Likewise for pretty-print ref. But I dunno. Maybe that is all going too far.Ok... so you mean we just use a loop like in
branch.c, and get rid ofshow_ref_array_items()andshow_ref_array_item().
(We can still use the optimization of reuse bufs)Yes, something like this:
builtin/for-each-ref.c +12
-2@@ -22,6 +22,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)struct ref_array array;struct ref_filter filter;struct ref_format format =REF_FORMAT_INIT ; +struct strbuf output =STRBUF_INIT ; +struct strbuf err =STRBUF_INIT ;struct option opts[] = { OPT_BIT('s', "shell", &format.quote_style,@@ -80,8 +82,16 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)if (!maxcount || array.nr < maxcount) maxcount = array.nr; -for (i =0 ; i < maxcount; i++) -show_ref_array_item (array.items[i], &format); + +for (i =0 ; i < maxcount; i++) { +strbuf_reset (&output); +if (format_ref_array_item (array.items[i], &format, &output, &err)) +die ("%s" , err.buf); +fwrite (output.buf,1 , output.len, stdout); +putchar ('\n' ); + } + +strbuf_release (&output);ref_array_clear (&array);return 0 ; }
Quoted 4 lines
It is dropping a few lines by assuming that the error buf is only touched when we return an error (which IMHO is a reasonable assumption to make).
I agree, err buffer does not need to be reused.
Quoted 1 line
-Peff
Thanks.
--
ZheNing Hu