From: Jeff King <peff@peff.net>
To: "René Scharfe" <l.s.r@web.de>
Cc: Git List <git@vger.kernel.org>
Subject: Re: [PATCH] cat-file: speed up default format
Date: Tue, 16 Jun 2026 07:12:37 -0400 [thread overview]
Message-ID: <20260616111237.GA687438@coredump.intra.peff.net> (raw)
In-Reply-To: <10a33614-837f-4166-aa30-6de28b052692@web.de>
On Mon, Jun 15, 2026 at 11:53:07PM +0200, René Scharfe wrote:
> > IMHO that is probably not worth it for a custom parsing system just for
> > cat-file. But if we were to finally unify ref-filter and cat-file (and
> > even --pretty=format) then it would probably worth doing this kind of
> > pre-parsing.
> It could be worth it for cat-file alone if we find the right balance, as
> it already does do a separate parsing step, but that is awkward with its
> mark_query checks all over the place and remembers only object property
> requirements and no other format string details.
Yeah, getting rid of the mark_query pass was a nice side effect of
having a true parse step.
> Making the opcodes small should be beneficial. We need only a handful
> of them, so a byte each should suffice. We can use a strbuf for that.
>
> We can also store literal characters in there. An opcode plus with a
> payload char incurs an overhead of 50%, which sounds high, but at least
> the default format only has two of them and it's much better than
> storing pointer plus size for an overhead of more than 90% in case of a
> single char.
True, and it's a size win if the literal portions tend to be small
(fewer than 15 bytes). You do lose out on the ability to strbuf_add()
them in one go, though. So lots more strbuf_grow() checks, etc. If you
really wanted to get fancy, you could follow the opcode with a length
represented as a variable-sized integer, followed by the literal bytes.
I'm not sure that Git's formatting code needs to squeeze out quite that
much performance, though.
> That gets us closer to native speed, at least on an Apple M1:
>
> Benchmark 1: ./git_fp cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
> Time (mean ± σ): 992.7 ms ± 3.2 ms [User: 967.5 ms, System: 23.8 ms]
> Range (min … max): 990.1 ms … 1000.7 ms 10 runs
>
> Benchmark 2: ./git_switch cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
> Time (mean ± σ): 991.8 ms ± 1.6 ms [User: 967.0 ms, System: 23.3 ms]
> Range (min … max): 989.3 ms … 994.4 ms 10 runs
>
> Benchmark 3: ./git cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
> Time (mean ± σ): 985.8 ms ± 2.9 ms [User: 960.5 ms, System: 23.6 ms]
> Range (min … max): 982.9 ms … 993.0 ms 10 runs
>
> Benchmark 4: ./git cat-file --batch-all-objects --batch-check='%(objectname) %(objecttype) %(objectsize)'
> Time (mean ± σ): 982.1 ms ± 3.2 ms [User: 956.7 ms, System: 23.6 ms]
> Range (min … max): 979.2 ms … 989.2 ms 10 runs
OK, so we managed another 1%. But I'm skeptical that this linear opcode
technique is where we want to go in the long run, if we're ever going to
unify formatters.
One, for more advanced features like %(if) we'd want to support some
notion of hierarchy and recursion. We have to speculatively format the
inner part and see if it is empty.
Though I guess that is possible with a linearized set of opcodes. If you
turn "%(if)%(HEAD)%(then)*%(end)" into:
FMT_IF
FMT_HEAD
FMT_THEN
FMT_LITERAL
*
FMT_END
then I guess you just start a sub-execution of the opcodes after FMT_IF
and tell it to stop when it sees FMT_THEN. It does mean that the opcodes
themselves need to control the program counter, rather than the executor
blindly walking along the opcodes and asking them to put stuff in the
output. Whereas I think if the parser builds a tree of structs then this
falls out pretty naturally.
The second thing is that many of the ref-filter atoms have options, and
those options have to be represented in the opcodes. That works
naturally if each opcode gets its own struct (either with a big union,
or true polymorphism). But representing "%(describe:match=versions/v*)"
in opcodes sounds gross. Now you need opcodes to represent the options
(and maybe "no more options"), and some way of encoding arbitrary input
for those option arguments.
-Peff
next prev parent reply other threads:[~2026-06-16 11:12 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-14 16:28 [PATCH] cat-file: speed up default format René Scharfe
2026-06-15 7:27 ` Patrick Steinhardt
2026-06-15 16:53 ` Jeff King
2026-06-15 17:06 ` Jeff King
2026-06-15 21:53 ` René Scharfe
2026-06-16 11:12 ` Jeff King [this message]
2026-06-16 22:14 ` René Scharfe
2026-06-16 22:21 ` Junio C Hamano
2026-06-15 21:53 ` René Scharfe
2026-06-16 11:15 ` Jeff King
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260616111237.GA687438@coredump.intra.peff.net \
--to=peff@peff.net \
--cc=git@vger.kernel.org \
--cc=l.s.r@web.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox