packed-refs: use `fwrite()` when passing refs verbatim

The `write_with_updates()` function uses a `struct ref_iterator` to
iterate over all refs to write to the temporary packed-refs file. It
receives the iterator from `packed_ref_iterator_begin()` which takes a
snapshot of the 'packed-refs' file.

While writing to the new packed-refs file, writes are routed via
`write_packed_entry()` which uses `fprintf()`. Even for references which
haven't changed, we use the same mechanism. This is slow since we need
to do string parsing and formatting. Instead, let's track the position
of unchanged references in the snapshot iterator and directly write
verbatim via `fwrite()`.

With this, any sanitization which was happening as a side effect of
reformatting is now lost. But that was never the job of this section of
the code, since the main intention is to simply rewrite the remaining
refs post deletion of the selective few.

This removes the unnecessary formatting operation involved. We can see a
consistent ~20% performance improvement when deleting from packed
references.

Benchmark 1: update-ref: delete ref (refcount = 100000, revision = master)
  Time (mean ± σ):      28.7 ms ±   1.7 ms    [User: 22.5 ms, System: 5.9 ms]
  Range (min … max):    26.7 ms …  33.3 ms    46 runs

Benchmark 2: update-ref: delete ref (refcount = 100000, revision = b4/kn-speedup-packed-refs)
  Time (mean ± σ):      23.8 ms ±   1.2 ms    [User: 17.5 ms, System: 6.0 ms]
  Range (min … max):    22.1 ms …  27.7 ms    56 runs

Summary
  update-ref: delete ref (refcount = 100000, revision = b4/kn-speedup-packed-refs) ran
    1.21 ± 0.09 times faster than update-ref: delete ref (refcount = 100000, revision = master)

Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
next
Karthik Nayak 2026-10-07 13:20:16 +02:00 committed by Junio C Hamano
parent a018953688
commit a61035fab9
1 changed files with 13 additions and 3 deletions

View File

@ -879,6 +879,9 @@ struct packed_ref_iterator {
/* The current position in the snapshot's buffer: */
const char *pos;

/* The starting position of the current ref record. */
const char *record_start;

/* The end of the part of the buffer that will be iterated over: */
const char *eof;

@ -933,6 +936,7 @@ static int next_record(struct packed_ref_iterator *iter)
if (iter->pos == iter->eof)
return ITER_DONE;

iter->record_start = iter->pos;
iter->base.ref.flags = REF_ISPACKED;
p = iter->pos;

@ -1530,9 +1534,15 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re
}

if (cmp < 0) {
/* Pass the old reference through. */
if (write_packed_entry(out, iter->ref.name,
iter->ref.oid, iter->ref.peeled_oid))
const struct packed_ref_iterator *packed_iter =
(const struct packed_ref_iterator *)iter;
size_t len = packed_iter->pos - packed_iter->record_start;

/*
* Skip any formatting and directly write to the packed-refs file
* when deleting references and writing the remaining refs verbatim.
*/
if (fwrite(packed_iter->record_start, len, 1, out) != 1)
goto write_error;

if ((ok = ref_iterator_advance(iter)) != ITER_OK) {