Subject: [PATCH v2] [GSOC] ref-filter: use single strbuf for all output

From: "ZheNing Hu via GitGitGadget" <[email-masked]>
Date: Wed, 07 Apr 2021 15:26:48 +0000

From: ZheNing Hu <[email-masked]>

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.

Signed-off-by: ZheNing Hu <[email-masked]>
---
    [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.
      
          Signed-off-by: ZheNing Hu <[email-masked]>
      
       ## 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(-)

diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index cb9c81a04606..d630402230f3 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -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;
 }
diff --git a/ref-filter.c b/ref-filter.c
index f0bd32f71416..27bbf9b6c8ac 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -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)
 {
diff --git a/ref-filter.h b/ref-filter.h
index 19ea4c413409..eb7e79a6676d 100644
--- a/ref-filter.h
+++ b/ref-filter.h
@@ -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
-- 
gitgitgadget