Thread (42 messages) 42 messages, 5 authors, 2d ago

Re: weird quadratic reftable behavior, was: Re: [PATCH 3/3] t5551: pack refs after creating many tags

From: Patrick Steinhardt <hidden>
Date: 2026-07-01 06:17:52

On Tue, Jun 30, 2026 at 07:58:50PM -0400, Jeff King wrote:
On Tue, Jun 30, 2026 at 07:47:02PM -0400, Jeff King wrote:
quoted
There was one other oddity I didn't quite resolve. You may notice the
gross reftable.orig stuff in hyperfine. I originally wrote this as:

    git for-each-ref --format="delete %(refname)" | git update-ref --stdin

but for some reason that causes the subsequent update-ref to loop
infinitely on merged_iter_next_entry(). It does so reliably, but I can't
reproduce it outside of hyperfine. Super weird, and I'm sure I'm missing
something obvious.
Ah, maybe not infinite, but probably quadratic. The key is that you have
to delete a lot of refs and then try to insert them again. So with this
script:

  nr=$1; shift
  rm -rf .git
  
  git init --ref-format=reftable
  blob=$(echo foo | git hash-object -w --stdin)
  seq -f "create refs/tags/foo-%g $blob" $nr >input
  git update-ref --stdin <input
  git for-each-ref --format="delete %(refname)" | git update-ref --stdin
  time git update-ref --stdin <input

I get results like this:

  nr   | runtime
  ------------
  1000 | 0.125s
  2000 | 0.454s
  4000 | 1.811s
  8000 | 7.091s

So for every doubling of the input size, the runtime quadruples. I guess
it is iterating through some deleted tombstone entries, but I'm not sure
why.

That's probably a more interesting and productive performance problem to
work on than micro-optimizing out the last few microseconds of writing. :)
This is a known issue, I think [1].

The problem here is the tombstoning: when you delete all references,
chances are that they are not truly gone but that every reference is
just tombstoned. The problem with this is that reading refs may now take
signifciantly more time as we cannot just say "this stack is empty".
Instead, we need to figure out that it is empty by processing all the
tombstones, and that takes a lot of time.

I remember that I did some digging back then and improved the status quo
quite significantly by optimizing `refs_verify_refname_available()`. I'm
sure there are more opportunities for optimization here though -- I have
a feeling that we for example exhaust the merged iterator until its end
when searching for a specific refname, where we could easily abort once
the observed tombstone name sorts lexicographically after the needle.

But eventually I decided to not care too much about this edge case, as
it seems very specific to this artificial benchmark scenario. Which of
course doesn't mean that it's not worth doing, I just had bigger fish to
fry and didn't get around to it yet.

Patrick

[1]: [ref]
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help