git: worktree, hoist Config() calls out of per-file loops#1990
Merged
Conversation
Contributor
Author
|
I was surprised by the speedup, but I guess it makes sense. The more files you are checking out, the more excessif amount of Config() read you are doing and removing it pay off a lot. |
ad0bf6f to
5aa8b97
Compare
35c4af1 to
498a276
Compare
Cache the *config.Config result and thread it through internal
functions instead of repeatedly calling w.r.Config() for each file
during checkout, reset, status, and add operations.
Previously, each worktree operation (Reset, Status, Add) called
Config() once per file plus overhead, resulting in N+4 disk I/O
operations (open, read, parse .git/config) where N is the number of
files. Now Config() is called once per public entry point and passed
to private functions via a new cfg parameter.
Changes:
- Reset() calls Config once; threaded through resetWorktreeToTree(),
resetWorktree(), checkoutChange(), checkoutChangeRegularFile(),
and checkoutFile()
- StatusWithOptions(), doAdd(), AddGlob() call Config once; threaded
through diffStagingWithWorktree(), checkoutChange(), and related
functions
- autoAddModifiedAndDeleted() passes cfg through checkoutFile()
No public API changes. No new struct fields.
Benchmark results (benchstat):
bench-before.txt bench-after.txt
sec/op sec/op
CloneLargeRepo-12 6.093 ± 38% 1.946 ± 15% -68.06%
CloneDeepRepo-12 3.878 ± 72% 2.143 ± 7% -44.75%
B/op B/op
CloneLargeRepo-12 151.1Mi ± 0% 137.5Mi ± 0% -9.02%
CloneDeepRepo-12 163.7Mi ± 0% 150.1Mi ± 0% -8.35%
allocs/op allocs/op
CloneLargeRepo-12 1013.4k ± 0% 805.3k ± 0% -20.54%
CloneDeepRepo-12 1113.7k ± 0% 905.6k ± 0% -18.69%
Assisted-by: OpenCode with Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Cedric BAIL <cedric.bail@appdirect.com>
498a276 to
da238a9
Compare
pjbgf
approved these changes
May 29, 2026
pjbgf
left a comment
Member
There was a problem hiding this comment.
@cedric-appdirect thanks for working on this. 🙇
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cache the
*config.Configresult at each public entry point and thread itthrough internal functions instead of repeatedly calling
w.r.Config()foreach file during checkout, reset, status, and add operations.
Addresses Tier 1 item 1 from #1956: "Cache r.Config() result outside the
checkout file loop, pass it through as a parameter."
Problem
On the filesystem storage backend, each
w.r.Config()call does:file open, full file read, INI parse, struct alloc. During checkout,
Config()was called N+4 times (once per file incopyObjectToWorktreeplus overhead), where N is the number of files being checked out. The config
cannot change mid-operation, so all but the first call are wasted work.
Solution
Call
w.r.Config()once at each public entry point (Reset,StatusWithOptions,doAdd,AddGlob), then pass the resulting*config.Configthrough all private functions in the call chain.Reset()threads cfg throughcontainsUnstagedChanges,resetWorktree,resetWorktreeToTree,checkoutChange,checkoutChangeRegularFile,checkoutFile,copyObjectToWorktreeStatusWithOptions()threads cfg throughstatus,diffStagingWithWorktree,getSubmodulesStatusdoAdd()/AddGlob()threads cfg throughdoAddFile,doAddDirectory,copyFileToStorage,fillEncodedObjectFromFileautoAddModifiedAndDeleted()threads cfg throughdoAddFileThe public
Submodules()method keeps its existing signature and delegatesto a new private
submodulesWithConfig(cfg)that internal callers usedirectly.
No public API changes. No new struct fields. No cached state.
Config() calls per operation
Reset(MergeReset)on N filesReset(HardReset)on N filesStatus()Addon N filesSubmodules()(public, standalone)Benchmark results
Known follow-up opportunities
A few internal paths still double-read Config because they call the public
w.Status()method (which has its ownw.r.Config()call):doAdd/AddGlob/autoAddModifiedAndDeletedcallw.Status()internally
checkoutChangeSubmodulecallsw.Submodule()which callsw.Submodules()preloadStatusinstatus.gohas its own Config() callThese are pre-existing and not regressions. Addressing them requires a
statusWithConfigprivate variant — a good candidate for a follow-up PR.