mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-03 13:14:41 +00:00
This is like #9556, but at the level of all other builds, including the resolver and installer. Going through PEP 517 to build a package is slow, so when building a package with the uv build backend, we can call into the uv build backend directly instead: No temporary virtual env, no temp venv sync, no python subprocess calls, no uv subprocess calls. This fast path is gated through preview. Since the uv wheel is not available at test time, I've manually confirmed the feature by comparing `uv venv && cargo run pip install . -v --preview --reinstall .` and `uv venv && cargo run pip install . -v --reinstall .`. When hacking the preview so that the python uv build backend works without the setting the direct build also (wheel built with `maturin build --profile profiling`), we can see the perfomance difference: ``` $ hyperfine --prepare "uv venv" --warmup 3 \ "UV_PREVIEW=1 target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --preview" \ "target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --find-links target/wheels/" Benchmark 1: UV_PREVIEW=1 target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --preview Time (mean ± σ): 33.1 ms ± 2.5 ms [User: 25.7 ms, System: 13.0 ms] Range (min … max): 29.8 ms … 47.3 ms 73 runs Benchmark 2: target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --find-links target/wheels/ Time (mean ± σ): 115.1 ms ± 4.3 ms [User: 54.0 ms, System: 27.0 ms] Range (min … max): 109.2 ms … 123.8 ms 25 runs Summary UV_PREVIEW=1 target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --preview ran 3.48 ± 0.29 times faster than target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --find-links target/wheels/ ``` Do we need a global option to disable the fast path? There is one for `uv build` because `--force-pep517` moves `uv build` much closer to a `pip install` from source that a user of a library would experience (See discussion at #9610), but uv overall doesn't really make guarantees around the build env of dependencies, so I consider the direct build a valid option. Best reviewed commit-by-commit, only the last commit is the actual implementation, while the preview mode introduction is just a refactoring touching too many files.
219 lines
7.4 KiB
Rust
219 lines
7.4 KiB
Rust
use std::str::FromStr;
|
|
|
|
use uv_bench::criterion::black_box;
|
|
use uv_bench::criterion::{criterion_group, criterion_main, measurement::WallTime, Criterion};
|
|
use uv_cache::Cache;
|
|
use uv_client::RegistryClientBuilder;
|
|
use uv_pypi_types::Requirement;
|
|
use uv_python::PythonEnvironment;
|
|
use uv_resolver::Manifest;
|
|
|
|
fn resolve_warm_jupyter(c: &mut Criterion<WallTime>) {
|
|
let run = setup(Manifest::simple(vec![Requirement::from(
|
|
uv_pep508::Requirement::from_str("jupyter==1.0.0").unwrap(),
|
|
)]));
|
|
c.bench_function("resolve_warm_jupyter", |b| b.iter(|| run(false)));
|
|
}
|
|
|
|
fn resolve_warm_jupyter_universal(c: &mut Criterion<WallTime>) {
|
|
let run = setup(Manifest::simple(vec![Requirement::from(
|
|
uv_pep508::Requirement::from_str("jupyter==1.0.0").unwrap(),
|
|
)]));
|
|
c.bench_function("resolve_warm_jupyter_universal", |b| b.iter(|| run(true)));
|
|
}
|
|
|
|
fn resolve_warm_airflow(c: &mut Criterion<WallTime>) {
|
|
let run = setup(Manifest::simple(vec![
|
|
Requirement::from(uv_pep508::Requirement::from_str("apache-airflow[all]==2.9.3").unwrap()),
|
|
Requirement::from(
|
|
uv_pep508::Requirement::from_str("apache-airflow-providers-apache-beam>3.0.0").unwrap(),
|
|
),
|
|
]));
|
|
c.bench_function("resolve_warm_airflow", |b| b.iter(|| run(false)));
|
|
}
|
|
|
|
// This takes >5m to run in CodSpeed.
|
|
// fn resolve_warm_airflow_universal(c: &mut Criterion<WallTime>) {
|
|
// let run = setup(Manifest::simple(vec![
|
|
// Requirement::from(uv_pep508::Requirement::from_str("apache-airflow[all]").unwrap()),
|
|
// Requirement::from(
|
|
// uv_pep508::Requirement::from_str("apache-airflow-providers-apache-beam>3.0.0").unwrap(),
|
|
// ),
|
|
// ]));
|
|
// c.bench_function("resolve_warm_airflow_universal", |b| b.iter(|| run(true)));
|
|
// }
|
|
|
|
criterion_group!(
|
|
uv,
|
|
resolve_warm_jupyter,
|
|
resolve_warm_jupyter_universal,
|
|
resolve_warm_airflow
|
|
);
|
|
criterion_main!(uv);
|
|
|
|
fn setup(manifest: Manifest) -> impl Fn(bool) {
|
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
// CodSpeed limits the total number of threads to 500
|
|
.max_blocking_threads(256)
|
|
.enable_all()
|
|
.build()
|
|
.unwrap();
|
|
|
|
let cache = Cache::from_path("../../.cache").init().unwrap();
|
|
let interpreter = PythonEnvironment::from_root("../../.venv", &cache)
|
|
.unwrap()
|
|
.into_interpreter();
|
|
let client = RegistryClientBuilder::new(cache.clone()).build();
|
|
|
|
move |universal| {
|
|
runtime
|
|
.block_on(resolver::resolve(
|
|
black_box(manifest.clone()),
|
|
black_box(cache.clone()),
|
|
black_box(&client),
|
|
&interpreter,
|
|
universal,
|
|
))
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
mod resolver {
|
|
use std::sync::LazyLock;
|
|
|
|
use anyhow::Result;
|
|
|
|
use uv_cache::Cache;
|
|
use uv_client::RegistryClient;
|
|
use uv_configuration::{
|
|
BuildOptions, Concurrency, ConfigSettings, Constraints, IndexStrategy, LowerBound,
|
|
PreviewMode, SourceStrategy,
|
|
};
|
|
use uv_dispatch::{BuildDispatch, SharedState};
|
|
use uv_distribution::DistributionDatabase;
|
|
use uv_distribution_types::{DependencyMetadata, IndexLocations};
|
|
use uv_install_wheel::linker::LinkMode;
|
|
use uv_pep440::Version;
|
|
use uv_pep508::{MarkerEnvironment, MarkerEnvironmentBuilder};
|
|
use uv_platform_tags::{Arch, Os, Platform, Tags};
|
|
use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment};
|
|
use uv_python::Interpreter;
|
|
use uv_resolver::{
|
|
FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, RequiresPython,
|
|
Resolver, ResolverEnvironment, ResolverOutput,
|
|
};
|
|
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
|
|
|
|
static MARKERS: LazyLock<MarkerEnvironment> = LazyLock::new(|| {
|
|
MarkerEnvironment::try_from(MarkerEnvironmentBuilder {
|
|
implementation_name: "cpython",
|
|
implementation_version: "3.11.5",
|
|
os_name: "posix",
|
|
platform_machine: "arm64",
|
|
platform_python_implementation: "CPython",
|
|
platform_release: "21.6.0",
|
|
platform_system: "Darwin",
|
|
platform_version: "Darwin Kernel Version 21.6.0: Mon Aug 22 20:19:52 PDT 2022; root:xnu-8020.140.49~2/RELEASE_ARM64_T6000",
|
|
python_full_version: "3.11.5",
|
|
python_version: "3.11",
|
|
sys_platform: "darwin",
|
|
}).unwrap()
|
|
});
|
|
|
|
static PLATFORM: Platform = Platform::new(
|
|
Os::Macos {
|
|
major: 21,
|
|
minor: 6,
|
|
},
|
|
Arch::Aarch64,
|
|
);
|
|
|
|
static TAGS: LazyLock<Tags> = LazyLock::new(|| {
|
|
Tags::from_env(&PLATFORM, (3, 11), "cpython", (3, 11), false, false).unwrap()
|
|
});
|
|
|
|
pub(crate) async fn resolve(
|
|
manifest: Manifest,
|
|
cache: Cache,
|
|
client: &RegistryClient,
|
|
interpreter: &Interpreter,
|
|
universal: bool,
|
|
) -> Result<ResolverOutput> {
|
|
let build_isolation = BuildIsolation::default();
|
|
let build_options = BuildOptions::default();
|
|
let concurrency = Concurrency::default();
|
|
let config_settings = ConfigSettings::default();
|
|
let exclude_newer = Some(
|
|
jiff::civil::date(2024, 8, 8)
|
|
.to_zoned(jiff::tz::TimeZone::UTC)
|
|
.unwrap()
|
|
.timestamp()
|
|
.into(),
|
|
);
|
|
let build_constraints = Constraints::default();
|
|
let flat_index = FlatIndex::default();
|
|
let hashes = HashStrategy::default();
|
|
let state = SharedState::default();
|
|
let index = InMemoryIndex::default();
|
|
let index_locations = IndexLocations::default();
|
|
let installed_packages = EmptyInstalledPackages;
|
|
let options = OptionsBuilder::new().exclude_newer(exclude_newer).build();
|
|
let sources = SourceStrategy::default();
|
|
let dependency_metadata = DependencyMetadata::default();
|
|
let conflicts = Conflicts::empty();
|
|
|
|
let python_requirement = if universal {
|
|
PythonRequirement::from_requires_python(
|
|
interpreter,
|
|
RequiresPython::greater_than_equal_version(&Version::new([3, 11])),
|
|
)
|
|
} else {
|
|
PythonRequirement::from_interpreter(interpreter)
|
|
};
|
|
|
|
let build_context = BuildDispatch::new(
|
|
client,
|
|
&cache,
|
|
build_constraints,
|
|
interpreter,
|
|
&index_locations,
|
|
&flat_index,
|
|
&dependency_metadata,
|
|
state,
|
|
IndexStrategy::default(),
|
|
&config_settings,
|
|
build_isolation,
|
|
LinkMode::default(),
|
|
&build_options,
|
|
&hashes,
|
|
exclude_newer,
|
|
LowerBound::default(),
|
|
sources,
|
|
concurrency,
|
|
PreviewMode::Enabled,
|
|
);
|
|
|
|
let markers = if universal {
|
|
ResolverEnvironment::universal(vec![])
|
|
} else {
|
|
ResolverEnvironment::specific(ResolverMarkerEnvironment::from(MARKERS.clone()))
|
|
};
|
|
|
|
let resolver = Resolver::new(
|
|
manifest,
|
|
options,
|
|
&python_requirement,
|
|
markers,
|
|
conflicts,
|
|
Some(&TAGS),
|
|
&flat_index,
|
|
&index,
|
|
&hashes,
|
|
&build_context,
|
|
installed_packages,
|
|
DistributionDatabase::new(client, &build_context, concurrency.downloads),
|
|
)?;
|
|
|
|
Ok(resolver.resolve().await?)
|
|
}
|
|
}
|