{"thread":{"id":"59913","subject":"[PATCH 0/2] [RFC] for-each-ref: add --count-matches mode","startedAt":"2023-06-26T15:11:04Z","lastAt":"2023-07-11T14:49:03Z","messageCount":12,"participants":["Derrick Stolee via GitGitGadget","Junio C Hamano","Jeff King","Phillip Wood","Derrick Stolee","René Scharfe"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"478792","messageId":"pull.1548.git.1687792197.gitgitgadget@gmail.com","threadId":"59913","inReplyTo":null,"subject":"[PATCH 0/2] [RFC] for-each-ref: add --count-matches mode","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T15:09:55Z","receivedAt":"2023-06-26T15:11:04Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"I'm leaving this as an RFC for now because I can't decide if this new option\nin git for-each-ref is good or if this needs an entirely new builtin. I'm\nopen to whatever people think is best, I'd just like a way to count matches\nbased on refspecs.\n\nThanks, -Stolee\n\nDerrick Stolee (2):\n  for-each-ref: extract ref output loop\n  for-each-ref: add --count-matches option\n\n Documentation/git-for-each-ref.txt |  5 ++\n builtin/for-each-ref.c             | 80 +++++++++++++++++++++---------\n ref-filter.c                       | 47 ++++++++++++++++++\n ref-filter.h                       |  7 +++\n t/perf/p1501-ref-iteration.sh      | 35 +++++++++++++\n t/t6300-for-each-ref.sh            | 28 +++++++++++\n 6 files changed, 179 insertions(+), 23 deletions(-)\n create mode 100755 t/perf/p1501-ref-iteration.sh\n\n\nbase-commit: d7d8841f67f29e6ecbad85a11805c907d0f00d5d\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1548%2Fderrickstolee%2Ffor-each-ref-count-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1548/derrickstolee/for-each-ref-count-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1548\n-- \ngitgitgadget\n"},{"id":"478793","messageId":"9121e027fb9f157878a9624ce6c834b69cd38472.1687792197.git.gitgitgadget@gmail.com","threadId":"59913","inReplyTo":"pull.1548.git.1687792197.gitgitgadget@gmail.com","subject":"[PATCH 2/2] for-each-ref: add --count-matches option","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T15:09:57Z","receivedAt":"2023-06-26T15:11:07Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nIn order to count references of different types based on their initial\nprefixes, there are two current approaches:\n\n 1. Run 'git for-each-ref' on all refs and parse the output to count\n    those that match each prefix.\n\n 2. Run 'git for-each-ref \"prefix/\"' and pipe that output to 'wc -l' to\n    count the number of output lines.\n\nEach of these approaches is wasteful as it requires sending the list of\nmatching reference names across a pipe plus the cost of parsing that\noutput.\n\nInstead, it would be helpful to have a Git command that counts the\nnumber of refs matching a list of patterns.\n\nThis change adds a new mode, '--count-matches' to 'git for-each-ref' so\nwe can make use of the existing infrastructure around parsing refspecs\nin the correct places. '--count' is already taken as a \"maximum number\"\nof refs to output.\n\nAn alternative approach could be to make a brand-new builtin that is\nfocused on counting ref matches. This would involve duplicating a bit of\ncode around parsing refpecs, but would not be terribly difficult. The\nactual overlap of implementation here with 'git for-each-ref' is small\nenough that we could instead extract this elsewhere. My gut feeling is\nthat this behavior doesn't merit a new builtin.\n\nThe implementation is extremely simple: iterate through all references\nand compare each ref to each refspec. On a match, increment a counter\nvalue for that refspec.\n\nIn the end, output each refspec followed by the count.\n\nIf all given refspecs were prefixes, then it is tempting to instead use\na counting behavior that we can use in things like \"how many OIDs start\nwith this short hex string?\" Navigating to the start and end of the\nrange of refs starting with that prefix is possible in the packed-refs\nfile. However, the records do not have a constant size so we cannot\ninfer the number of references in that range using the current format.\n(Perhaps, in the future we will have a ref storage system that allows\nthis kind of counting to be easy to do in O(log N) time.)\n\nA new performance test is included to check the performance of iterating\nthrough these references and counting them appropriately. This presents\na 3x improvement over the trivial piping through to 'wc -l', and that\nassumes there is a single pattern to match instead of multiple. We can\nsee that testing three patterns sequentially adds to the total time, but\ndoing a single process with --count-match continues to be as fast. (It's\ndifficult to tell since it _also_ matches the sum of the three for this\nexample repo.)\n\nTest                                                         this tree\n----------------------------------------------------------------------------\n1501.2: count refs/heads/: git for-each-ref | wc -l          0.01(0.00+0.01)\n1501.3: count refs/heads/: git for-each-ref --count-match    0.00(0.00+0.00)\n1501.4: count refs/tags/: git for-each-ref | wc -l           0.02(0.00+0.02)\n1501.5: count refs/tags/: git for-each-ref --count-match     0.00(0.00+0.00)\n1501.6: count refs/remotes: git for-each-ref | wc -l         0.15(0.08+0.07)\n1501.7: count refs/remotes: git for-each-ref --count-match   0.04(0.01+0.02)\n1501.8: count all patterns: git for-each-ref | wc -l         0.18(0.08+0.10)\n1501.9: count all patterns: git for-each-ref --count-match   0.04(0.02+0.02)\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n Documentation/git-for-each-ref.txt |  5 ++++\n builtin/for-each-ref.c             | 26 +++++++++++++++--\n ref-filter.c                       | 47 ++++++++++++++++++++++++++++++\n ref-filter.h                       |  7 +++++\n t/perf/p1501-ref-iteration.sh      | 35 ++++++++++++++++++++++\n t/t6300-for-each-ref.sh            | 28 ++++++++++++++++++\n 6 files changed, 145 insertions(+), 3 deletions(-)\n create mode 100755 t/perf/p1501-ref-iteration.sh\n\ndiff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt\nindex 1e215d4e734..74018755ed5 100644\n--- a/Documentation/git-for-each-ref.txt\n+++ b/Documentation/git-for-each-ref.txt\n@@ -42,6 +42,11 @@ OPTIONS\n \t`<pattern>`.  This option makes it stop after showing\n \tthat many refs.\n \n+--count-matches::\n+\tInstead of listing references according to the given format, output\n+\tthe number of references that match each pattern. Incompatible with\n+\t`--format`, `--sort`, and `--count`.\n+\n --sort=<key>::\n \tA field name to sort on.  Prefix `-` to sort in\n \tdescending order of the value.  When unspecified,\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex ce34940e3e6..e3db719bb87 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -50,6 +50,17 @@ static void filter_and_output_refs(struct repository *r,\n \tstrbuf_release(&output);\n }\n \n+static void count_and_output_patterns(struct ref_filter *filter)\n+{\n+\tuint32_t *counts = count_ref_patterns(filter);\n+\n+\tfor (int i = 0; filter->name_patterns && filter->name_patterns[i]; i++)\n+\t\tfprintf(stdout, \"%s %\"PRIu32\"\\n\",\n+\t\t\tfilter->name_patterns[i], counts[i]);\n+\n+\tfree(counts);\n+}\n+\n int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n {\n \tstruct ref_sorting *sorting;\n@@ -60,6 +71,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \tstruct ref_format format = REF_FORMAT_INIT;\n \tint from_stdin = 0;\n \tstruct strvec vec = STRVEC_INIT;\n+\tconst char *initial_format = \"%(objectname) %(objecttype)\\t%(refname)\";\n \n \tstruct option opts[] = {\n \t\tOPT_BIT('s', \"shell\", &format.quote_style,\n@@ -72,6 +84,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"quote placeholders suitably for Tcl\"), QUOTE_TCL),\n \t\tOPT_BOOL(0, \"omit-empty\",  &omit_empty,\n \t\t\tN_(\"do not output a newline after empty formatted refs\")),\n+\t\tOPT_BOOL(0, \"count-matches\", &count_matches,\n+\t\t\tN_(\"output number of references matching each pattern instead of any other output\")),\n \n \t\tOPT_GROUP(\"\"),\n \t\tOPT_INTEGER( 0 , \"count\", &maxcount, N_(\"show only <n> matched refs\")),\n@@ -93,7 +107,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \tmemset(&array, 0, sizeof(array));\n \tmemset(&filter, 0, sizeof(filter));\n \n-\tformat.format = \"%(objectname) %(objecttype)\\t%(refname)\";\n+\tformat.format = initial_format;\n \n \tgit_config(git_default_config, NULL);\n \n@@ -102,6 +116,9 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \t\terror(\"invalid --count argument: `%d'\", maxcount);\n \t\tusage_with_options(for_each_ref_usage, opts);\n \t}\n+\tif (count_matches &&\n+\t    (maxcount || format.format != initial_format || sorting_options.nr))\n+\t\tdie(\"--count-matches incompatible with --count, --format, or --sort\");\n \tif (HAS_MULTI_BITS(format.quote_style)) {\n \t\terror(\"more than one quoting style?\");\n \t\tusage_with_options(for_each_ref_usage, opts);\n@@ -131,8 +148,11 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \t}\n \n \tfilter.match_as_path = 1;\n-\tfilter_and_output_refs(the_repository, &array, &filter, &format,\n-\t\t\t       sorting, maxcount, omit_empty);\n+\tif (count_matches)\n+\t\tcount_and_output_patterns(&filter);\n+\telse\n+\t\tfilter_and_output_refs(the_repository, &array, &filter, &format,\n+\t\t\t\t       sorting, maxcount, omit_empty);\n \n \tref_array_clear(&array);\n \tfree_commit_list(filter.with_commit);\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 4991cd4f7a8..4e02a4ae98d 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2560,6 +2560,53 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int\n \treturn ret;\n }\n \n+struct filter_and_counts {\n+\tstruct ref_filter *filter;\n+\tuint32_t *counts;\n+};\n+\n+static int ref_filter_counter(const char *refname,\n+\t\t\t      const struct object_id *oid,\n+\t\t\t      int flag, void *cb_data)\n+{\n+\tstruct filter_and_counts *fc = cb_data;\n+\tconst char **pattern = fc->filter->name_patterns;\n+\tsize_t namelen = strlen(refname);\n+\tunsigned flags = fc->filter->ignore_case ? WM_CASEFOLD : 0;\n+\n+\tfor (int i = 0; *pattern; i++, pattern++) {\n+\t\tconst char *p = *pattern;\n+\t\tint plen = strlen(p);\n+\n+\t\tif ((plen <= namelen) &&\n+\t\t    !strncmp(refname, p, plen) &&\n+\t\t    (refname[plen] == '\\0' ||\n+\t\t     refname[plen] == '/' ||\n+\t\t     p[plen-1] == '/'))\n+\t\t\tfc->counts[i]++;\n+\t\telse if (!wildmatch(p, refname, flags))\n+\t\t\tfc->counts[i]++;\n+\t}\n+\treturn 0;\n+}\n+\n+uint32_t *count_ref_patterns(struct ref_filter *filter)\n+{\n+\tint size = 0;\n+\tstruct filter_and_counts fc = {\n+\t\t.filter = filter,\n+\t};\n+\n+\twhile (filter->name_patterns[size])\n+\t\tsize++;\n+\n+\tCALLOC_ARRAY(fc.counts, size);\n+\n+\tfor_each_fullref_in_pattern(filter, ref_filter_counter, &fc);\n+\n+\treturn fc.counts;\n+}\n+\n static int compare_detached_head(struct ref_array_item *a, struct ref_array_item *b)\n {\n \tif (!(a->kind ^ b->kind))\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 430701cfb76..b7e5a4f6a80 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -141,6 +141,13 @@ char *get_head_description(void);\n /*  Set up translated strings in the output. */\n void setup_ref_filter_porcelain_msg(void);\n \n+/*\n+ * Iterate over all references, counting how many match each filter\n+ * pattern. Returns an array of the counts with the ith count matching the\n+ * ith filter->name_pattern entry.\n+ */\n+uint32_t *count_ref_patterns(struct ref_filter *filter);\n+\n /*\n  * Print a single ref, outside of any ref-filter. Note that the\n  * name must be a fully qualified refname.\ndiff --git a/t/perf/p1501-ref-iteration.sh b/t/perf/p1501-ref-iteration.sh\nnew file mode 100755\nindex 00000000000..609487d20b0\n--- /dev/null\n+++ b/t/perf/p1501-ref-iteration.sh\n@@ -0,0 +1,35 @@\n+#!/bin/sh\n+\n+test_description='Ref iteration performance tests'\n+. ./perf-lib.sh\n+\n+test_perf_large_repo\n+\n+# Optimize ref backend store\n+test_expect_success 'setup' '\n+\tgit pack-refs\n+'\n+\n+for pattern in \"refs/heads/\" \"refs/tags/\" \"refs/remotes\"\n+do\n+\ttest_perf \"count $pattern: git for-each-ref | wc -l\" \"\n+\t\tgit for-each-ref $pattern | wc -l\n+\t\"\n+\n+\ttest_perf \"count $pattern: git for-each-ref --count-match\" \"\n+\t\tgit for-each-ref --count-matches $pattern\n+\t\"\n+done\n+\n+test_perf \"count all patterns: git for-each-ref | wc -l\" \"\n+\tgit for-each-ref refs/heads/ | wc -l &&\n+\tgit for-each-ref refs/tags/ | wc -l &&\n+\tgit for-each-ref refs/remotes/ | wc -l\n+\"\n+\n+test_perf \"count all patterns: git for-each-ref --count-match\" \"\n+\tgit for-each-ref --count-matches \\\n+\t\trefs/heads/ refs/tags/ refs/remotes/\n+\"\n+\n+test_done\ndiff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh\nindex 5c00607608a..001382956e4 100755\n--- a/t/t6300-for-each-ref.sh\n+++ b/t/t6300-for-each-ref.sh\n@@ -486,6 +486,34 @@ for i in \"--perl --shell\" \"-s --python\" \"--python --tcl\" \"--tcl --perl\"; do\n \t\"\n done\n \n+test_expect_success '--count-matches incompatible with some options' '\n+\tfor opt in \"--format=x\" \"--sort=refname\" \"--count=10\"\n+\tdo\n+\t\ttest_must_fail git for-each-ref --count-matches $opt refs/heads/ 2>err &&\n+\t\tgrep \"count-matches incompatible\" err || return 1\n+\tdone\n+'\n+\n+test_expect_success '--count-matches tallies the number matching each refspec' '\n+\tgit init multi-refs &&\n+\ttest_commit -C multi-refs A &&\n+\tgit -C multi-refs branch A &&\n+\tgit -C multi-refs branch pre/A &&\n+\ttest_commit -C multi-refs --no-tag B &&\n+\tgit -C multi-refs branch B &&\n+\tgit -C multi-refs for-each-ref --count-matches \\\n+\t\trefs/heads/ refs/heads/pre/ refs/tags/ \"*A*\" >actual &&\n+\n+\tcat >expect <<-EOF &&\n+\trefs/heads/ 4\n+\trefs/heads/pre/ 1\n+\trefs/tags/ 1\n+\t*A* 3\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'setup for upstream:track[short]' '\n \ttest_commit two\n '\n-- \ngitgitgadget\n"},{"id":"478794","messageId":"4aa261d9de1ec054f9e1cdecf700b29b57cedf56.1687792197.git.gitgitgadget@gmail.com","threadId":"59913","inReplyTo":"pull.1548.git.1687792197.gitgitgadget@gmail.com","subject":"[PATCH 1/2] for-each-ref: extract ref output loop","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T15:09:56Z","receivedAt":"2023-06-26T15:11:09Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <derrickstolee@github.com>\n\nIn preparation for a new output mode of 'git for-each-ref', extract the\nloop that outputs references into a static method. No functional change\nhere.\n\nSigned-off-by: Derrick Stolee <derrickstolee@github.com>\n---\n builtin/for-each-ref.c | 58 ++++++++++++++++++++++++++----------------\n 1 file changed, 36 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 695fc8f4a5e..ce34940e3e6 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -17,17 +17,47 @@ static char const * const for_each_ref_usage[] = {\n \tNULL\n };\n \n-int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n+static void filter_and_output_refs(struct repository *r,\n+\t\t\t\t   struct ref_array *array,\n+\t\t\t\t   struct ref_filter *filter,\n+\t\t\t\t   struct ref_format *format,\n+\t\t\t\t   struct ref_sorting *sorting,\n+\t\t\t\t   int maxcount,\n+\t\t\t\t   int omit_empty)\n {\n \tint i;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct strbuf output = STRBUF_INIT;\n+\n+\tfilter_refs(array, filter, FILTER_REFS_ALL);\n+\tfilter_ahead_behind(r, format, array);\n+\n+\tref_array_sort(sorting, array);\n+\n+\tif (!maxcount || array->nr < maxcount)\n+\t\tmaxcount = array->nr;\n+\tfor (i = 0; i < maxcount; i++) {\n+\t\tstrbuf_reset(&err);\n+\t\tstrbuf_reset(&output);\n+\t\tif (format_ref_array_item(array->items[i], format, &output, &err))\n+\t\t\tdie(\"%s\", err.buf);\n+\t\tfwrite(output.buf, 1, output.len, stdout);\n+\t\tif (output.len || !omit_empty)\n+\t\t\tputchar('\\n');\n+\t}\n+\n+\tstrbuf_release(&err);\n+\tstrbuf_release(&output);\n+}\n+\n+int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n+{\n \tstruct ref_sorting *sorting;\n \tstruct string_list sorting_options = STRING_LIST_INIT_DUP;\n-\tint maxcount = 0, icase = 0, omit_empty = 0;\n+\tint maxcount = 0, icase = 0, omit_empty = 0, count_matches = 0;\n \tstruct ref_array array;\n \tstruct ref_filter filter;\n \tstruct ref_format format = REF_FORMAT_INIT;\n-\tstruct strbuf output = STRBUF_INIT;\n-\tstruct strbuf err = STRBUF_INIT;\n \tint from_stdin = 0;\n \tstruct strvec vec = STRVEC_INIT;\n \n@@ -101,25 +131,9 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \t}\n \n \tfilter.match_as_path = 1;\n-\tfilter_refs(&array, &filter, FILTER_REFS_ALL);\n-\tfilter_ahead_behind(the_repository, &format, &array);\n+\tfilter_and_output_refs(the_repository, &array, &filter, &format,\n+\t\t\t       sorting, maxcount, omit_empty);\n \n-\tref_array_sort(sorting, &array);\n-\n-\tif (!maxcount || array.nr < maxcount)\n-\t\tmaxcount = array.nr;\n-\tfor (i = 0; i < maxcount; i++) {\n-\t\tstrbuf_reset(&err);\n-\t\tstrbuf_reset(&output);\n-\t\tif (format_ref_array_item(array.items[i], &format, &output, &err))\n-\t\t\tdie(\"%s\", err.buf);\n-\t\tfwrite(output.buf, 1, output.len, stdout);\n-\t\tif (output.len || !omit_empty)\n-\t\t\tputchar('\\n');\n-\t}\n-\n-\tstrbuf_release(&err);\n-\tstrbuf_release(&output);\n \tref_array_clear(&array);\n \tfree_commit_list(filter.with_commit);\n \tfree_commit_list(filter.no_commit);\n-- \ngitgitgadget\n\n"},{"id":"478799","messageId":"xmqqbkh25hfr.fsf@gitster.g","threadId":"59913","inReplyTo":"9121e027fb9f157878a9624ce6c834b69cd38472.1687792197.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] for-each-ref: add --count-matches option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-26T16:14:48Z","receivedAt":"2023-06-26T16:17:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Each of these approaches is wasteful as it requires sending the list of\n> matching reference names across a pipe plus the cost of parsing that\n> output.\n>\n> Instead, it would be helpful to have a Git command that counts the\n> number of refs matching a list of patterns.\n\nFor for-each-ref (which can be reused by branch and tag for no\ncost), I am on the fence but slightly in favor (partly because the\ncode has already been written).  But I have to wonder where changes\nthat come from the above reasoning need to end, though.\n\nDo we count branches and tags because \"git branch --list | wc -l\" is\ntoo costly?  Should we teach \"git remote\" the same trick?  How about\n\"git stash list\"?  \"git show-index $pack_index\"?  \"git ls-files\"?\n\nHow do we decide where to draw the line?  When a command invocation\nin a large repository can produce records exceeding a million?\n"},{"id":"478868","messageId":"20230627073007.GD1226768@coredump.intra.peff.net","threadId":"59913","inReplyTo":"9121e027fb9f157878a9624ce6c834b69cd38472.1687792197.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] for-each-ref: add --count-matches option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-06-27T07:30:07Z","receivedAt":"2023-06-27T07:30:37Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jun 26, 2023 at 03:09:57PM +0000, Derrick Stolee via GitGitGadget wrote:\n\n> +for pattern in \"refs/heads/\" \"refs/tags/\" \"refs/remotes\"\n> +do\n> +\ttest_perf \"count $pattern: git for-each-ref | wc -l\" \"\n> +\t\tgit for-each-ref $pattern | wc -l\n> +\t\"\n> +\n> +\ttest_perf \"count $pattern: git for-each-ref --count-match\" \"\n> +\t\tgit for-each-ref --count-matches $pattern\n> +\t\"\n> +done\n\nI don't think this is a very realistic perf test, because for-each-ref\nis doing a bunch of work to generate its default format, only to have\n\"wc\" throw most of it away. Doing:\n\n  git for-each-ref --format='%(refname)' | wc -l\n\nis much better (obviously you have to remember to do that if you care\nabout optimizing your command, but that's true of --count-matches, too).\n\nRunning hyperfine with three variants shows that the command above is\ncompetitive with --count-matches, though slightly slower (hyperfine\ncomplains about short commands and outliers because these runtimes are\nso tiny in the first place; I omitted those warnings from the output\nbelow for readability):\n\n  Benchmark 1: ./git-for-each-ref refs/remotes/ | wc -l\n    Time (mean ± σ):       6.1 ms ±   0.2 ms    [User: 3.0 ms, System: 3.6 ms]\n    Range (min … max):     5.6 ms …   7.1 ms    397 runs\n  \n  Benchmark 2: ./git-for-each-ref --format=\"%(refname)\" refs/remotes/ | wc -l\n    Time (mean ± σ):       3.3 ms ±   0.2 ms    [User: 2.2 ms, System: 1.5 ms]\n    Range (min … max):     3.0 ms …   4.0 ms    774 runs\n  \n  Benchmark 3: ./git-for-each-ref --count-matches refs/remotes/\n    Time (mean ± σ):       2.4 ms ±   0.1 ms    [User: 1.5 ms, System: 0.9 ms]\n    Range (min … max):     2.2 ms …   3.4 ms    1018 runs\n  \n  Summary\n    './git-for-each-ref --count-matches refs/remotes/' ran\n      1.33 ± 0.10 times faster than './git-for-each-ref --format=\"%(refname)\" refs/remotes/ | wc -l'\n      2.48 ± 0.17 times faster than './git-for-each-ref refs/remotes/ | wc -l'\n\nI will note this is an unloaded multi-core system, which gives the piped\nversion a slight edge. Total CPU is probably more interesting than\nwall-clock time, but all of these are so short that I think the results\nshould be taken with a pretty big grain of salt (I had to switch from\nthe \"powersave\" to \"performance\" CPU governor just to get more\nconsistent results).\n\n-Peff\n"},{"id":"478877","messageId":"f6fd39bc-65d4-76e3-94b4-9163194c89dd@gmail.com","threadId":"59913","inReplyTo":"20230627073007.GD1226768@coredump.intra.peff.net","subject":"Re: [PATCH 2/2] for-each-ref: add --count-matches option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-06-27T10:05:29Z","receivedAt":"2023-06-27T10:08:24Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 27/06/2023 08:30, Jeff King wrote:\n> On Mon, Jun 26, 2023 at 03:09:57PM +0000, Derrick Stolee via GitGitGadget wrote:\n> \n>> +for pattern in \"refs/heads/\" \"refs/tags/\" \"refs/remotes\"\n>> +do\n>> +\ttest_perf \"count $pattern: git for-each-ref | wc -l\" \"\n>> +\t\tgit for-each-ref $pattern | wc -l\n>> +\t\"\n>> +\n>> +\ttest_perf \"count $pattern: git for-each-ref --count-match\" \"\n>> +\t\tgit for-each-ref --count-matches $pattern\n>> +\t\"\n>> +done\n> \n> I don't think this is a very realistic perf test, because for-each-ref\n> is doing a bunch of work to generate its default format, only to have\n> \"wc\" throw most of it away. Doing:\n> \n>    git for-each-ref --format='%(refname)' | wc -l\n\nThat's a good point. I wondered if using a short fixed format string was \neven better so I tried\n\ngit init test\ncd test\ngit commit --allow-empty -m initial\nseq 0 100000 | sed \"s:\\(.*\\):create refs/heads/some-prefix/\\1 $(git \nrev-parse HEAD):\" | git update-ref --stdin\ngit pack-refs --all\nhyperfine -L fmt \"\",\"--format=%\\(refname\\)\",\"--format=x\" 'git \nfor-each-ref {fmt} refs/heads/ | wc -l'\n\nWhich gives\n\nBenchmark 1: git for-each-ref  refs/heads/ | wc -l\n   Time (mean ± σ):      1.150 s ±  0.010 s    [User: 0.494 s, System: \n0.637 s]\n   Range (min … max):    1.140 s …  1.170 s    10 runs\n\nBenchmark 2: git for-each-ref --format=%\\(refname\\) refs/heads/ | wc -l\n   Time (mean ± σ):      66.0 ms ±   0.3 ms    [User: 58.9 ms, System: \n9.5 ms]\n   Range (min … max):    65.2 ms …  67.1 ms    43 runs\n\nBenchmark 3: git for-each-ref --format=x refs/heads/ | wc -l\n   Time (mean ± σ):      63.0 ms ±   0.5 ms    [User: 54.3 ms, System: \n9.6 ms]\n   Range (min … max):    62.3 ms …  65.4 ms    44 runs\n\nSummary\n   git for-each-ref --format=x refs/heads/ | wc -l ran\n     1.05 ± 0.01 times faster than git for-each-ref \n--format=%\\(refname\\) refs/heads/ | wc -l\n    18.25 ± 0.20 times faster than git for-each-ref  refs/heads/ | wc -l\n\nSo on my somewhat slower machine the default format is over an order of \nmagnitude slower than using either --format=%(refname) or --format=x and \nthe short fixed format is marginally faster. I haven't applied stolee's \npatch but the 3 or 4 times improvement mentioned in the commit message \nseems likely to be from not processing the default format. One thing to \nnote is that we're not comparing like-with-like when more than one \npattern is given as --count-matches gives a separate count for each pattern.\n\nI'm a bit suspicious of the massive speed up I'm seeing by avoiding the \ndefault format but it appears to be repeatable.\n\nBest Wishes\n\nPhillip\n\n> is much better (obviously you have to remember to do that if you care\n> about optimizing your command, but that's true of --count-matches, too).\n> \n> Running hyperfine with three variants shows that the command above is\n> competitive with --count-matches, though slightly slower (hyperfine\n> complains about short commands and outliers because these runtimes are\n> so tiny in the first place; I omitted those warnings from the output\n> below for readability):\n> \n>    Benchmark 1: ./git-for-each-ref refs/remotes/ | wc -l\n>      Time (mean ± σ):       6.1 ms ±   0.2 ms    [User: 3.0 ms, System: 3.6 ms]\n>      Range (min … max):     5.6 ms …   7.1 ms    397 runs\n>    \n>    Benchmark 2: ./git-for-each-ref --format=\"%(refname)\" refs/remotes/ | wc -l\n>      Time (mean ± σ):       3.3 ms ±   0.2 ms    [User: 2.2 ms, System: 1.5 ms]\n>      Range (min … max):     3.0 ms …   4.0 ms    774 runs\n>    \n>    Benchmark 3: ./git-for-each-ref --count-matches refs/remotes/\n>      Time (mean ± σ):       2.4 ms ±   0.1 ms    [User: 1.5 ms, System: 0.9 ms]\n>      Range (min … max):     2.2 ms …   3.4 ms    1018 runs\n>    \n>    Summary\n>      './git-for-each-ref --count-matches refs/remotes/' ran\n>        1.33 ± 0.10 times faster than './git-for-each-ref --format=\"%(refname)\" refs/remotes/ | wc -l'\n>        2.48 ± 0.17 times faster than './git-for-each-ref refs/remotes/ | wc -l'\n> \n> I will note this is an unloaded multi-core system, which gives the piped\n> version a slight edge. Total CPU is probably more interesting than\n> wall-clock time, but all of these are so short that I think the results\n> should be taken with a pretty big grain of salt (I had to switch from\n> the \"powersave\" to \"performance\" CPU governor just to get more\n> consistent results).\n> \n> -Peff\n"},{"id":"478915","messageId":"xmqqmt0khik5.fsf@gitster.g","threadId":"59913","inReplyTo":"f6fd39bc-65d4-76e3-94b4-9163194c89dd@gmail.com","subject":"Re: [PATCH 2/2] for-each-ref: add --count-matches option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-27T18:22:02Z","receivedAt":"2023-06-27T18:22:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> I'm a bit suspicious of the massive speed up I'm seeing by avoiding\n> the default format but it appears to be repeatable.\n\nThis is interesting.  The default format over just 'x' is to\nconcatenate the refname with hexadecimal form of the object name,\nwhile (at least as the code originally was intended):\n\n * the cost of parsing the \"--format=<format>\" string into series of\n   atoms is one-time O(1), and there is nothing specially tricky in\n   there.\n\n * the cost of computing these atoms should be miniscule, as\n   in the default format, the per-ref cost come from these:\n\n   - populate_value() -> get_refname() -> show_ref() for the refname\n     would merely be a xstrdup() of the refname.\n\n   - populate_value() -> grab_oid() -> do_grab_oid() should be using\n     O_FULL so there shouldn't be any find_unique_abbrev() cost.\n     Just binary-to-hex.\n\n   - populate_value() -> get_object() -> oid_object_info_extended() ->\n     grab_common_values() asks for ATOM_OBJECTTYPE that should be a\n     single xstrdup(), but oid_object_info_extended() needs to parse\n     the object, but .info.contentp ought to be NULL and we should\n     not be calling parse_object_buffer().\n\nSo it might be worth looking into where the time is going (didn't\nPeff or somebody do that a few years ago, though?) when using the\ndefault format and optimize or special case the codepath, but the\nresponses I have seen from two of you makes me suspect that the new\noption is not the best general approach.\n\nThanks.\n\n\n\n\n"},{"id":"478928","messageId":"20230627195900.GC1280909@coredump.intra.peff.net","threadId":"59913","inReplyTo":"xmqqmt0khik5.fsf@gitster.g","subject":"Re: [PATCH 2/2] for-each-ref: add --count-matches option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-06-27T19:59:00Z","receivedAt":"2023-06-27T19:59:05Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jun 27, 2023 at 11:22:02AM -0700, Junio C Hamano wrote:\n\n>    - populate_value() -> get_object() -> oid_object_info_extended() ->\n>      grab_common_values() asks for ATOM_OBJECTTYPE that should be a\n>      single xstrdup(), but oid_object_info_extended() needs to parse\n>      the object, but .info.contentp ought to be NULL and we should\n>      not be calling parse_object_buffer().\n\nThis is the atom that I expect to give most of the cost. We should\ndefinitely be computing that just with a pack lookup (and chasing deltas\nthrough the pack, though commits usually aren't deltas). But without\nthat atom, I think we would not even mmap the idx or pack file at all.\n\nThe ref-filter code is also pretty keen to malloc() little strings. When\nwe're measuring 5ms total runtimes and there's just not that much actual\nwork to do, those little things start to matter more.\n\n> So it might be worth looking into where the time is going (didn't\n> Peff or somebody do that a few years ago, though?) when using the\n> default format and optimize or special case the codepath,[...]\n\nRunning:\n\n perf record -g git for-each-ref --format='%(refname) %(objecttype)' >/dev/null\n perf script report flamegraph\n\nin linux.git shows that we spend a lot of time faulting in .idx pages.\n\nA lot of the time is attributed to ref_array_sort(), but I think that's\na red herring. It lazy-loads the atom values, so that's where most of\nthe work is happening (though I would have thought it would just do the\nrefname atom, not all of them; but I guess it doesn't really matter\nsince we'll eventually need them to print anyway). Commenting out the\nsort call just shows the same time spent in format_ref_array_item().\n\nThe patches you're thinking of are probably:\n\n  https://lore.kernel.org/git/YTNpQ7Od1U%2F5i0R7@coredump.intra.peff.net/\n\nwhich are mostly about micro-optimizing out those mallocs. They do still\nhelp a little in the '%(refname)' case, but not nearly as much as\ndropping '%(objecttype)' does.\n\n(Using --format=x in theory drops out some mallocs, but I wasn't able to\nmeasure any actual speedup).\n\n> [...]but the\n> responses I have seen from two of you makes me suspect that the new\n> option is not the best general approach.\n\nYeah, my feeling is that with the reduced format, eliminating the\npipe/wc overhead is a pretty small improvement.\n\n-Peff\n"},{"id":"478957","messageId":"776c3682-d2eb-d2d7-3ea8-4a7db8cd7842@gmail.com","threadId":"59913","inReplyTo":"f6fd39bc-65d4-76e3-94b4-9163194c89dd@gmail.com","subject":"Re: [PATCH 2/2] for-each-ref: add --count-matches option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-06-28T13:12:38Z","receivedAt":"2023-06-28T13:12:46Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 27/06/2023 11:05, Phillip Wood wrote:\n> On 27/06/2023 08:30, Jeff King wrote:\n>> I don't think this is a very realistic perf test, because for-each-ref\n>> is doing a bunch of work to generate its default format, only to have\n>> \"wc\" throw most of it away. Doing:\n>>\n>>    git for-each-ref --format='%(refname)' | wc -l\n> \n> That's a good point. I wondered if using a short fixed format string was \n> even better so I tried\n> \n> git init test\n> cd test\n> git commit --allow-empty -m initial\n> seq 0 100000 | sed \"s:\\(.*\\):create refs/heads/some-prefix/\\1 $(git \n> rev-parse HEAD):\" | git update-ref --stdin\n> git pack-refs --all\n> hyperfine -L fmt \"\",\"--format=%\\(refname\\)\",\"--format=x\" 'git \n> for-each-ref {fmt} refs/heads/ | wc -l'\n> \n> Which gives\n> [...] \n> Summary\n>    git for-each-ref --format=x refs/heads/ | wc -l ran\n>      1.05 ± 0.01 times faster than git for-each-ref \n> --format=%\\(refname\\) refs/heads/ | wc -l\n>     18.25 ± 0.20 times faster than git for-each-ref  refs/heads/ | wc -l\n> [...] \n> I'm a bit suspicious of the massive speed up I'm seeing by avoiding the \n> default format but it appears to be repeatable.\n\nHaving seen Peff's mail [1] I realized that my test repo above is \nlooking up the commit from a loose object. If I repack the repository \nthen the default format is still slower than using \"--format=%(refname)\" \nbut is much more competitive.\n\n$ git repack -a\nEnumerating objects: 2, done.\nCounting objects: 100% (2/2), done.\nWriting objects: 100% (2/2), done.\nTotal 2 (delta 0), reused 0 (delta 0), pack-reused 0\n\n$ hyperfine -L fmt \"\",\"--format=%\\(refname\\)\",\"--format=x\" 'git \nfor-each-ref {fmt} refs/heads/ | wc'\nBenchmark 1: git for-each-ref  refs/heads/ | wc -l\n   Time (mean ± σ):     111.4 ms ±   1.4 ms    [User: 96.9 ms, System: \n19.6 ms]\n   Range (min … max):   109.6 ms … 115.1 ms    25 runs\n\nBenchmark 2: git for-each-ref --format=%\\(refname\\) refs/heads/ | wc -l\n   Time (mean ± σ):      66.7 ms ±   0.7 ms    [User: 59.5 ms, System: \n9.5 ms]\n   Range (min … max):    65.6 ms …  68.2 ms    42 runs\n\nBenchmark 3: git for-each-ref --format=x refs/heads/ | wc -l\n   Time (mean ± σ):      63.4 ms ±   0.7 ms    [User: 56.3 ms, System: \n8.0 ms]\n   Range (min … max):    61.9 ms …  65.1 ms    44 runs\n\nSummary\n   git for-each-ref --format=x refs/heads/ | wc -l ran\n     1.05 ± 0.02 times faster than git for-each-ref \n--format=%\\(refname\\) refs/heads/ | wc -l\n     1.76 ± 0.03 times faster than git for-each-ref  refs/heads/ | wc -l\n\nSo it seems most of the slowdown I was seeing yesterday was due it \nlooking up a loose object. I'm surprised repacking makes such a \ndifference in a repository that only contains two objects.\n\nBest Wishes\n\nPhillip\n\n[1] \nhttps://lore.kernel.org/git/20230627195900.GC1280909@coredump.intra.peff.net\n"},{"id":"478968","messageId":"xmqqzg4jcy62.fsf@gitster.g","threadId":"59913","inReplyTo":"776c3682-d2eb-d2d7-3ea8-4a7db8cd7842@gmail.com","subject":"Re: [PATCH 2/2] for-each-ref: add --count-matches option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-28T17:08:21Z","receivedAt":"2023-06-28T17:08:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> So it seems most of the slowdown I was seeing yesterday was due it\n> looking up a loose object. I'm surprised repacking makes such a\n> difference in a repository that only contains two objects.\n\nIf we compare what is done in packfile.c:packed_object_info() and\nobject-file.c:loose_object_info() when we are only interested in\nfinding out the object type, there aren't that many differences\nin the set of system calls each codepath needs to make.\n\n * The packfile codepath needs to open and mmap *.pack and *.idx,\n   binary search in the .idx for the object location, then read a\n   few bytes from .pack, before being able to decode the header to\n   find out the type.\n\n * The loose object codepath needs to open and mmap the loose object\n   file, read a few bytes from there, before being abole to decode\n   the header to find out the type. After that, it needs to munmap.\n\nThe cost of open/mmap for packfile codepath amortises over number of\nobjects (hence number of refs) very well.  If there are many refs\nthat point at the same object, cache object layer will kick in to\navoid disk access for second and subsequent accesses to the same\nobject, but it helps both codepaths equally, so there should not be\nmuch difference either way.\n\nThanks for a interesting piece of food for thought.\n"},{"id":"479344","messageId":"4715a469-8c68-8dcc-0254-9b7b857c3722@github.com","threadId":"59913","inReplyTo":"20230627073007.GD1226768@coredump.intra.peff.net","subject":"Re: [PATCH 2/2] for-each-ref: add --count-matches option","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2023-07-10T16:51:25Z","receivedAt":"2023-07-10T16:51:32Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 6/27/2023 1:30 AM, Jeff King wrote:\n>On Mon, Jun 26, 2023 at 03:09:57PM +0000, Derrick Stolee via GitGitGadget wrote:\n>\n>>+for pattern in \"refs/heads/\" \"refs/tags/\" \"refs/remotes\"\n>>+do\n>>+   test_perf \"count $pattern: git for-each-ref | wc -l\" \"\n>>+       git for-each-ref $pattern | wc -l\n>>+   \"\n>>+\n>>+   test_perf \"count $pattern: git for-each-ref --count-match\" \"\n>>+       git for-each-ref --count-matches $pattern\n>>+   \"\n>>+done\n>\n>I don't think this is a very realistic perf test, because for-each-ref\n>is doing a bunch of work to generate its default format, only to have\n>\"wc\" throw most of it away. Doing:\n>\n>  git for-each-ref --format='%(refname)' | wc -l\n>\n>is much better (obviously you have to remember to do that if you care\n>about optimizing your command, but that's true of --count-matches, too).\nThank you for pointing this out! I'll be sure to modify the test and the\nanalysis about it.\nThe other check I need to compare is when multiple refspecs are provided\nat the same time, which I do believe still is a valuable thing to combine\ninto a single process instead of multiple pipes to 'wc'.\nDid you have any thoughts on whether or not this works as an option in\n'git for-each-ref' or would be better broken into a new builtin?\nThanks,\n-Stolee\n"},{"id":"479379","messageId":"e77ff965-cad1-dee3-a0a4-aa41249a875d@web.de","threadId":"59913","inReplyTo":"f6fd39bc-65d4-76e3-94b4-9163194c89dd@gmail.com","subject":"Re: [PATCH 2/2] for-each-ref: add --count-matches option","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2023-07-11T14:48:29Z","receivedAt":"2023-07-11T14:49:03Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 27.06.23 um 12:05 schrieb Phillip Wood:\n> On 27/06/2023 08:30, Jeff King wrote:\n>> On Mon, Jun 26, 2023 at 03:09:57PM +0000, Derrick Stolee via GitGitGadget wrote:\n>>\n>>> +for pattern in \"refs/heads/\" \"refs/tags/\" \"refs/remotes\"\n>>> +do\n>>> +    test_perf \"count $pattern: git for-each-ref | wc -l\" \"\n>>> +        git for-each-ref $pattern | wc -l\n>>> +    \"\n>>> +\n>>> +    test_perf \"count $pattern: git for-each-ref --count-match\" \"\n>>> +        git for-each-ref --count-matches $pattern\n>>> +    \"\n>>> +done\n>>\n>> I don't think this is a very realistic perf test, because for-each-ref\n>> is doing a bunch of work to generate its default format, only to have\n>> \"wc\" throw most of it away. Doing:\n>>\n>>    git for-each-ref --format='%(refname)' | wc -l\n>\n> That's a good point. I wondered if using a short fixed format string was even better so I tried\n>\n> git init test\n> cd test\n> git commit --allow-empty -m initial\n> seq 0 100000 | sed \"s:\\(.*\\):create refs/heads/some-prefix/\\1 $(git rev-parse HEAD):\" | git update-ref --stdin\n> git pack-refs --all\n> hyperfine -L fmt \"\",\"--format=%\\(refname\\)\",\"--format=x\" 'git for-each-ref {fmt} refs/heads/ | wc -l'\n>\n> Which gives\n>\n> Benchmark 1: git for-each-ref  refs/heads/ | wc -l\n>   Time (mean ± σ):      1.150 s ±  0.010 s    [User: 0.494 s, System: 0.637 s]\n>   Range (min … max):    1.140 s …  1.170 s    10 runs\n>\n> Benchmark 2: git for-each-ref --format=%\\(refname\\) refs/heads/ | wc -l\n>   Time (mean ± σ):      66.0 ms ±   0.3 ms    [User: 58.9 ms, System: 9.5 ms]\n>   Range (min … max):    65.2 ms …  67.1 ms    43 runs\n>\n> Benchmark 3: git for-each-ref --format=x refs/heads/ | wc -l\n>   Time (mean ± σ):      63.0 ms ±   0.5 ms    [User: 54.3 ms, System: 9.6 ms]\n>   Range (min … max):    62.3 ms …  65.4 ms    44 runs\n>\n> Summary\n>   git for-each-ref --format=x refs/heads/ | wc -l ran\n>     1.05 ± 0.01 times faster than git for-each-ref --format=%\\(refname\\) refs/heads/ | wc -l\n>    18.25 ± 0.20 times faster than git for-each-ref  refs/heads/ | wc -l\n\nYou don't need no \"x\", by the way; using the empty string saves some\ncycles for me.  In my Git clone, no special setup, 9931 refs:\n\nBenchmark 1: git for-each-ref --format=x | wc -l\n  Time (mean ± σ):      25.1 ms ±   0.1 ms    [User: 8.7 ms, System: 16.8 ms]\n  Range (min … max):    24.9 ms …  25.6 ms    109 runs\n\nBenchmark 2: git for-each-ref --format= | wc -l\n  Time (mean ± σ):      24.6 ms ±   0.1 ms    [User: 8.3 ms, System: 16.8 ms]\n  Range (min … max):    24.4 ms …  25.3 ms    110 runs\n\nSummary\n  git for-each-ref --format= | wc -l ran\n    1.02 ± 0.01 times faster than git for-each-ref --format=x | wc -l\n\nRené\n\n"}]}