{{ message }}
Repository navigation
Conversation
When a new base doesn't fit in a pack's delta base cache, libgit2 frees every cached base that isn't in use. Before 9c62aaa it freed the eight least recently used, but finding those meant scanning the whole cache on every eviction, and dropping everything turned out faster: "the most used entries are quickly back into the cache". They come back by being rebuilt, though. Each flush also discards the bases that later reads keep returning to, and those reads inflate them and apply their deltas again. That costs most when the bases in use are just over the limit, as when reading every file at a commit of a large repository. Keep the entries on a list, most recently used first, so the cache knows what to evict without scanning: a hit moves an entry to the front in constant time, and adding a base frees unused entries from the back only until it fits. If the entries in use leave no room, don't cache the base, where the old loop never ended. An entry goes on the list only once it is in the map, so a failure to add it there no longer leaves the cache counting memory it can't free. The unused last_usage and use_ctr go away. Reading a 60k file checkout from a 1G repo takes 12% less CPU at the default 16 MiB, generating patches for its last 300 commits 6% less, and indexing the packs of smaller repos 3-4% less.
This branch has not been deployed
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.

When a new base doesn't fit in a pack's delta base cache, libgit2 frees every cached base that isn't in use. Before 9c62aaa it freed the eight least recently used, but finding those meant scanning the whole cache on every eviction, and dropping everything turned out faster, since "the most used entries are quickly back into the cache".
They come back by being rebuilt, though. Each flush also discards the bases that later reads keep returning to, and those reads inflate them and apply their deltas again. That costs most when the bases in use are just over the limit, as when reading every file at a commit of a large repository.
This keeps the entries on a doubly linked list in order of use, so the cache knows what to evict without scanning. A hit moves an entry to the front in constant time, and adding a base frees unused entries from the back only until it fits. If the entries in use leave no room, the base isn't cached; the old loop never ended in that case.
Performance
Measured on macOS (Apple silicon), comparing
mainwith this change at the default 16 MiB.With
benchmarks/cli(git2CLI, hyperfine, wall time):git.cin git (v2.45.0)crates/wasmtime/src/runtime/store.rsin wasmtimefile://hyperfine runs one build's runs and then the other's, so a drifting machine shows up as a difference. To control for that, I also timed the library directly. Each round ran both builds back to back, and the table gives the median over five rounds of each round's CPU-time ratio:
log -p)file://clone)Measured this way, building a pack shows no difference. That suggests the clone's +2.6% above is spurious.
In every workload, reads found a base in the cache more often, and 11–41% fewer bytes of bases were evicted. Peak memory is 0–7% higher, because the cache stays near its limit instead of emptying and refilling.
Built for wasm32, where rebuilding a base costs more, reading every file at HEAD of google-cloud-python takes 23% less CPU.