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

@ -29,6 +29,7 @@ uv-static = { workspace = true }
uv-types = { workspace = true }
uv-virtualenv = { workspace = true }
uv-warnings = { workspace = true }
uv-workspace = { workspace = true }
anstream = { workspace = true }
fs-err = { workspace = true }

View file

@ -38,6 +38,7 @@ use uv_python::{Interpreter, PythonEnvironment};
use uv_static::EnvVars;
use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, SourceBuildTrait};
use uv_warnings::warn_user_once;
use uv_workspace::WorkspaceCache;
pub use crate::error::{Error, MissingHeaderCause};
@ -269,6 +270,7 @@ impl SourceBuild {
version_id: Option<&str>,
locations: &IndexLocations,
source_strategy: SourceStrategy,
workspace_cache: &WorkspaceCache,
config_settings: ConfigSettings,
build_isolation: BuildIsolation<'_>,
build_stack: &BuildStack,
@ -294,6 +296,7 @@ impl SourceBuild {
fallback_package_name,
locations,
source_strategy,
workspace_cache,
&default_backend,
)
.await
@ -396,6 +399,7 @@ impl SourceBuild {
version_id,
locations,
source_strategy,
workspace_cache,
build_stack,
build_kind,
level,
@ -466,6 +470,7 @@ impl SourceBuild {
package_name: Option<&PackageName>,
locations: &IndexLocations,
source_strategy: SourceStrategy,
workspace_cache: &WorkspaceCache,
default_backend: &Pep517Backend,
) -> Result<(Pep517Backend, Option<Project>), Box<Error>> {
match fs::read_to_string(source_tree.join("pyproject.toml")) {
@ -496,6 +501,7 @@ impl SourceBuild {
install_path,
locations,
source_strategy,
workspace_cache,
)
.await
.map_err(Error::Lowering)?;
@ -857,6 +863,7 @@ async fn create_pep517_build_environment(
version_id: Option<&str>,
locations: &IndexLocations,
source_strategy: SourceStrategy,
workspace_cache: &WorkspaceCache,
build_stack: &BuildStack,
build_kind: BuildKind,
level: BuildOutput,
@ -957,6 +964,7 @@ async fn create_pep517_build_environment(
install_path,
locations,
source_strategy,
workspace_cache,
)
.await
.map_err(Error::Lowering)?;