Cache workspace discovery (#12096)

Reduce the overhead of `uv run` in large workspaces. Instead of
re-discovering the entire workspace each time we resolve the metadata of
a member, we can the discovered set of workspace members. Care needs to
be taken to not cache the discovery for `uv init`, `uv add` and `uv
remove`, which change the definitions of workspace members.

Below is apache airflow e3fe06382df4b19f2c0de40ce7c0bdc726754c74 `uv run
python` with a minimal payload. With this change, we avoid a ~350ms
overhead of each `uv run` invocation.

```
$ hyperfine --warmup 2 \
    "uv run --no-dev python -c \"print('hi')\"" \
    "uv-profiling run --no-dev python -c \"print('hi')\""
Benchmark 1: uv run --no-dev python -c "print('hi')"
  Time (mean ± σ):     492.6 ms ±   7.0 ms    [User: 393.2 ms, System: 97.1 ms]
  Range (min … max):   482.3 ms … 501.5 ms    10 runs
 
Benchmark 2: uv-profiling run --no-dev python -c "print('hi')"
  Time (mean ± σ):     129.7 ms ±   2.5 ms    [User: 105.4 ms, System: 23.2 ms]
  Range (min … max):   126.0 ms … 136.1 ms    22 runs
 
Summary
  uv-profiling run --no-dev python -c "print('hi')" ran
    3.80 ± 0.09 times faster than uv run --no-dev python -c "print('hi')"
```

The profile after those change below. We still spend a large chunk in
toml parsing (both `uv.lock` and `pyproject.toml`), but it's not
excessive anymore.


![image](https://github.com/user-attachments/assets/6fe78510-7e25-48ee-8a6d-220ee98ad120)
This commit is contained in:
konsti 2025-03-10 22:03:30 +01:00 committed by GitHub
parent 15663eab26
commit e843433b07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 381 additions and 154 deletions

View file

@ -38,6 +38,7 @@ use uv_types::{
AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, EmptyInstalledPackages, HashStrategy,
InFlight,
};
use uv_workspace::WorkspaceCache;
#[derive(Debug, Error)]
pub enum BuildDispatchError {
@ -94,6 +95,7 @@ pub struct BuildDispatch<'a> {
source_build_context: SourceBuildContext,
build_extra_env_vars: FxHashMap<OsString, OsString>,
sources: SourceStrategy,
workspace_cache: WorkspaceCache,
concurrency: Concurrency,
preview: PreviewMode,
}
@ -116,6 +118,7 @@ impl<'a> BuildDispatch<'a> {
hasher: &'a HashStrategy,
exclude_newer: Option<ExcludeNewer>,
sources: SourceStrategy,
workspace_cache: WorkspaceCache,
concurrency: Concurrency,
preview: PreviewMode,
) -> Self {
@ -137,8 +140,8 @@ impl<'a> BuildDispatch<'a> {
exclude_newer,
source_build_context: SourceBuildContext::default(),
build_extra_env_vars: FxHashMap::default(),
sources,
workspace_cache,
concurrency,
preview,
}
@ -200,6 +203,10 @@ impl BuildContext for BuildDispatch<'_> {
self.index_locations
}
fn workspace_cache(&self) -> &WorkspaceCache {
&self.workspace_cache
}
async fn resolve<'data>(
&'data self,
requirements: &'data [Requirement],
@ -417,6 +424,7 @@ impl BuildContext for BuildDispatch<'_> {
version_id,
self.index_locations,
sources,
self.workspace_cache(),
self.config_settings.clone(),
self.build_isolation,
&build_stack,