summaryrefslogtreecommitdiff
path: root/contrib/perf/README
blob: 65a5653a53dd59d9ac8d37b95f3140352803cf53 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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.