Subject: Performance regression in "update" hooks

From: Patrick Steinhardt <[email-masked]>
Date: Mon, 2 Mar 2026 08:17:41 +0100

Hi,

Bencher has alerted me that there's been two performance regressions in
git-receive-pack(1) [1] and git-fetch(1) [2].

The first one is quite easy to reproduce with the benchmarks at [3] and
bisects to fc148b146a (receive-pack: convert update hooks to new API,
2026-01-28):

  $ cd receive-refs
  $ ./run --revisions /path/to/your/git/repo \
      fc148b146ad41be71a7852c4867f0773cbfe1ff9~,fc148b146ad41be71a7852c4867f0773cbfe1ff9 \
      --parameter-list refformat reftable \
      --parameter-list refcount 10000

  Benchmark 1: receive: many refs (refformat = reftable, refcount = 10000, revision = fc148b146ad41be71a7852c4867f0773cbfe1ff9~)
    Time (mean ± σ):     182.0 ms ±   2.7 ms    [User: 91.5 ms, System: 89.3 ms]
    Range (min … max):   175.8 ms … 185.0 ms    15 runs

  Benchmark 2: receive: many refs (refformat = reftable, refcount = 10000, revision = fc148b146ad41be71a7852c4867f0773cbfe1ff9)
    Time (mean ± σ):     484.6 ms ±  27.6 ms    [User: 176.2 ms, System: 376.1 ms]
    Range (min … max):   406.2 ms … 495.1 ms    10 runs

  Summary
    receive: many refs (refformat = reftable, refcount = 10000, revision = fc148b146ad41be71a7852c4867f0773cbfe1ff9~) ran
      2.66 ± 0.16 times faster than receive: many refs (refformat = reftable, refcount = 10000, revision = fc148b146ad41be71a7852c4867f0773cbfe1ff9)

I've Cc'd Adrian.

The other performance regression seems to be present in both
git-receive-pack(1) and git-fetch(1) and happens between e6e9f13364
(Sync with 'master', 2026-02-25) and ebd1da8b75 (Merge branch
'cx/fetch-display-ubfix' into next, 2026-02-26). It took me a while to
reproduce as my local Git configuration was hiding the regression, but I
have been able to bisect this to 452b12c2e0 (builtin/maintenance: use
"geometric" strategy by default, 2026-02-24).

The problem here is rather simple though. The benchmark fetches 10,000
refs into the repository, and before the commit we didn't do anything
about them. But after the commit we now have per-data-structure tasks,
and the result is that we thus end up packing refs. That's also why the
regression isn't present in the reftable backend, as it wouldn't need
any optimization.

So I'd consider this to be a bug in the benchmarking infrastructure
itself that I'll fix by disabling auto-maintenance.

Thanks!

Patrick

[1]: https://bencher.dev/perf/git?lower_value=false&upper_value=false&lower_boundary=false&upper_boundary=false&x_axis=date_time&branches=595859eb-071c-48e9-97cf-195e0a3d6ed1&testbeds=02dcb8ad-6873-494c-aabc-9a6237601308&benchmarks=e3553193-aefc-40a4-8816-9c1bdc1838a4%2Ccd00a2a1-0fd1-416a-9812-cfd3e9b4fdb8&measures=63dafffb-98c4-4c27-ba43-7112cae627fc&start_time=1765177145759&end_time=1772434745759&tab=plots&plot=6887f804-2bbc-4219-8211-55b6440fd5c0&plots_search=6887f804-2bbc-4219-8211-55b6440fd5c0&key=true&reports_per_page=4&branches_per_page=8&testbeds_per_page=8&benchmarks_per_page=8&plots_per_page=8&reports_page=1&branches_page=1&testbeds_page=1&benchmarks_page=1&plots_page=1
[2]: https://bencher.dev/perf/git?lower_value=false&upper_value=false&lower_boundary=false&upper_boundary=false&x_axis=date_time&branches=595859eb-071c-48e9-97cf-195e0a3d6ed1&testbeds=02dcb8ad-6873-494c-aabc-9a6237601308&benchmarks=196480c8-64d1-4768-a3e2-ac3c5f75a26e%2Cb422ed57-2b09-474b-a85f-2d71ba7ca46b&measures=63dafffb-98c4-4c27-ba43-7112cae627fc&start_time=1765177141331&end_time=1772434741331&tab=plots&plot=4134acc8-9194-454c-9d71-f41b44ab969d&plots_search=4134acc8-9194-454c-9d71-f41b44ab969d&key=true&reports_per_page=4&branches_per_page=8&testbeds_per_page=8&benchmarks_per_page=8&plots_per_page=8&reports_page=1&branches_page=1&testbeds_page=1&benchmarks_page=1&plots_page=1
[3]: https://gitlab.com/gitlab-org/data-access/git/benchmarks