Three performance patches, measured on Linux/amd64 against git.git (1017 refs, of which 1008 annotated tags) and linux.git (946 refs, 945 tags, 3.7 GB pack). 01-git-peel-decoration.patch Applies to the git submodule, log-tree.c. add_ref_decoration() calls parse_object() on every annotated tag to follow it to its commit, inflating all of them. The ref backend already hands the callback a peeled object id when packed-refs has one, so use that instead. Output is byte-identical; git's own t4202-log, t3200-branch and t6300-for-each-ref pass. Commit and log pages: about 4 ms saved per request on a repo with ~1000 annotated tags. Cost scales with tag count, so repos with few tags see little. 03-git-decoration-type-elision.patch Applies to the git submodule, log-tree.c, on top of 01. 01 stopped add_ref_decoration() from inflating every tag object, but two odb_read_object_info() calls per ref survived it: one to classify the ref target, one to classify what it peels to. Each is a binary search through the pack index, and on linux.git that index is 389 MB covering 11.8 million objects, so a commit page paid roughly 1900 of them. Under CGI the process is new every request, so the page table for that index is rebuilt each time too. Neither lookup is needed. packed-refs only records a peeled line for refs pointing at a tag object, so when peeled_oid is present the type is already known; and the peeled object is only ever used as a decoration key, so lookup_unknown_object() serves without asking the object store what it is. Frame render on an idle i7-7700, cgit cache off, averaged over 60 random commits: linux.git 12.6 ms -> 2.0 ms (946 refs, 945 tags, 3.7 GB pack) git.git 6.5 ms -> 2.2 ms (1017 refs, 164 MB pack) A control serving the same objects through a repository with one ref instead of 946 costs 1.7 ms, so this closes most of what is left. The esi-diff fragment is unaffected (25.4 ms either way): it has no subject line, so it never asks for decorations. Output is byte-identical: git log --decorate over 20000 linux.git commits and all of git.git, for-each-ref and tag -l with peeled fields, and 6000 cgit commit and diff pages. git's t4202-log, t3200-branch, t6300-for-each-ref and t7004-tag pass (976 tests), as does cgit's own suite. 02-cgit-output-buffering.patch Applies to cgit: html.c, html.h, cache.c, filter.c, ui-shared.c, cgit.c. html_raw() did one unbounded write(2) per call. A 1.7 MB diff took 99151 write syscalls averaging 18 bytes. This adds a 64 KB buffer and flushes it wherever fd 1 is redirected or handed to another writer: the cache slot, exec filters, and the end of the HTTP headers (git's archive code writes straight to fd 1 for snapshots and clone data). 99151 writes become 27. Large diffs 19% faster, side-by-side diffs 49% faster, tree and log pages about 70% faster. Note the two ordering traps found while writing this: fill_slot() fstat()s the cache file to record its size, so the buffer must be flushed before that, not in unlock_slot(); and the headers must be flushed before any body, because snapshots bypass html_raw entirely. Both together, on cgit's own test suite: all 14 test files pass, and output is byte-identical to stock across commit, log, tree, refs, diff and both diff modes of the ESI fragment.