Skip to content

gitindex: optimize git index time by ~21%#1036

Open
clemlesne wants to merge 1 commit intosourcegraph:mainfrom
clemlesne:perf/optimize-git-index-time
Open

gitindex: optimize git index time by ~21%#1036
clemlesne wants to merge 1 commit intosourcegraph:mainfrom
clemlesne:perf/optimize-git-index-time

Conversation

@clemlesne
Copy link
Copy Markdown
Contributor

@clemlesne clemlesne commented Apr 1, 2026

Summary

  • Eliminate redundant enry classification: rank() was calling enry.IsGenerated/IsVendor/IsTest per document, duplicating work already done by DetermineFileCategory. Now uses cached doc.Category. Moved DetermineFileCategory to Builder.Add() to overlap with catfile I/O.
  • Skip redundant binary check: ShardBuilder.Add() re-scanned all content for null bytes even though DocChecker.Check already did this. Now skipped when Category is already set.
  • Single-byte varint fast path: ~80% of posting list deltas are < 128. append(slice, byte) is cheaper than append(slice, slice...).
  • Slab allocator for file content: Replaced ~29K individual make([]byte, size) with 16MB slab sub-slicing (3-index slices for safety), reducing GC pressure.
  • GC tuning for batch indexing: GOGC=-1 + GOMEMLIMIT=2GiB minimizes madvise syscall overhead. Both respect user env var overrides.

Benchmark (hyperfine)

Measured with hyperfine on kubernetes/kubernetes (29,226 files, ~230MB, no ctags):

hyperfine --warmup 3 --min-runs 15 \
  --prepare 'rm -rf /tmp/zoekt-index-hyperfine' \
  'zoekt-git-index-baseline -index /tmp/zoekt-index-hyperfine -disable_ctags /tmp/k8s-bench' \
  'zoekt-git-index-optimized -index /tmp/zoekt-index-hyperfine -disable_ctags /tmp/k8s-bench'
Command Mean [s] Min [s] Max [s] Relative
baseline (main) 5.987 ± 0.419 5.590 7.001 1.24 ± 0.09
optimized (PR) 4.813 ± 0.100 4.690 5.058 1.00

1.24x faster (mean), with notably lower variance (σ=0.100 vs σ=0.419).

Behavioral notes

  • rank() now uses DetermineFileCategory's mutually exclusive categories instead of independent enry.IsGenerated/IsVendor/IsTest checks. A file matching multiple categories (e.g. generated test file) previously received multiple rank penalties; now it receives only the highest-priority one. This changes intra-shard document ordering slightly (<0.02% shard size difference) but does not affect search correctness.
  • For SkipReasonTooManyTrigrams files, DetermineFileCategory now receives the original content (previously it received the marker string due to content replacement in ShardBuilder.Add). This is more correct — DetermineFileCategory was already designed to handle this case.

Test plan

  • go test ./... full suite passes
  • Output shards have identical file counts
  • ShardBuilder.Add still performs binary check + DetermineFileCategory for direct callers (merge path, AddFile) where Category == FileCategoryMissing
  • Slab sub-slices use 3-index slices to prevent accidental overwrites
  • GOGC and GOMEMLIMIT env vars are respected when set by the user

🤖 Generated with Claude Code

…etes)

Reduce wall-clock indexing time by eliminating redundant work, reducing
heap allocations, and tuning GC for batch workloads.

Measured with hyperfine (15 runs, 3 warmup) indexing kubernetes/kubernetes
(29,226 files, ~230 MB, ctags disabled):

  | Command          | Mean [s]      | Min [s] | Max [s] | Relative     |
  |:-----------------|:-------------:|--------:|--------:|:------------:|
  | baseline (main)  | 5.987 ± 0.419 |   5.590 |   7.001 | 1.24 ± 0.09  |
  | optimized (PR)   | 4.813 ± 0.100 |   4.690 |   5.058 | 1.00         |

Key changes:

- rank(): replace redundant enry.IsGenerated/IsVendor/IsTest calls with
  the already-computed doc.Category from DetermineFileCategory. Move
  DetermineFileCategory into Builder.Add so it overlaps with catfile I/O
  and is skipped in ShardBuilder.Add when already set.

- ShardBuilder.Add: skip the second bytes.IndexByte binary scan when
  DocChecker.Check already ran (doc.Category != FileCategoryMissing).

- newSearchableString: add single-byte varint fast path — ~80 % of
  posting-list deltas are < 128 and append(s, byte) avoids the slice
  header setup of append(s, slice...).

- indexCatfileBlobs: replace ~29 K individual make([]byte, size) with a
  contentSlab that sub-slices from 16 MB buffers (3-index slices to
  prevent cross-file corruption).

- zoekt-git-index main: set GOGC=-1 + GOMEMLIMIT=2 GiB for batch
  indexing to cut madvise syscall overhead.  Both honour the
  corresponding env vars when the user overrides them.
@clemlesne clemlesne force-pushed the perf/optimize-git-index-time branch from 89a951b to b2b2e85 Compare April 1, 2026 17:17
@keegancsmith keegancsmith self-requested a review April 2, 2026 19:36
@keegancsmith
Copy link
Copy Markdown
Member

Thanks, I'm out for easter weekend so will take a look Tuesday.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants