Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,27 @@ jobs:
- name: "Extract artifact archive"
run: tar -xvf benchmarks-walltime.tar

- name: "Install system dependencies"
run: |
sudo apt-get update
sudo apt-get install -y libsasl2-dev libldap2-dev libkrb5-dev

- name: "Clone airflow workspace"
run: |
git init airflow
git -C airflow remote add origin https://github.com/apache/airflow.git
git -C airflow fetch --depth 1 origin 7fa400745ac7aebc7cc4ec21d3a047e9fb258310
git -C airflow checkout FETCH_HEAD

- name: "Create venv"
run: ./target/debug/uv venv --cache-dir .cache

- name: "Sync airflow workspace"
run: ./target/debug/uv sync --directory airflow --cache-dir .cache

- name: "Prime airflow run"
run: ./target/debug/uv run --directory airflow --cache-dir .cache python -V

- name: "Run benchmarks"
uses: CodSpeedHQ/action@db35df748deb45fdef0960669f57d627c1956c30 # v4.13.1
with:
Expand Down Expand Up @@ -125,6 +143,19 @@ jobs:
cargo run --bin uv -- pip compile test/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
cargo run --bin uv -- pip compile test/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache

- name: "Clone airflow workspace"
run: |
git init airflow
git -C airflow remote add origin https://github.com/apache/airflow.git
git -C airflow fetch --depth 1 origin 7fa400745ac7aebc7cc4ec21d3a047e9fb258310
git -C airflow checkout FETCH_HEAD

- name: "Sync airflow workspace"
run: cargo run --bin uv -- sync --directory airflow --cache-dir .cache

- name: "Prime airflow run"
run: cargo run --bin uv -- run --directory airflow --cache-dir .cache python -V

- name: "Build benchmarks"
run: cargo codspeed build --profile profiling -p uv-bench

Expand Down
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions crates/uv-bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ name = "uv"
path = "benches/uv.rs"
harness = false

[[bench]]
name = "airflow_run"
path = "benches/airflow_run.rs"
harness = false

[dev-dependencies]
uv = { path = "../uv", default-features = false }
uv-cache = { workspace = true }
uv-cli = { workspace = true }
uv-client = { workspace = true }
uv-configuration = { workspace = true }
uv-dispatch = { workspace = true }
Expand All @@ -43,9 +50,13 @@ uv-types = { workspace = true }
uv-workspace = { workspace = true }

anyhow = { workspace = true }
clap = { workspace = true }
criterion = { version = "4.0.3", default-features = false, package = "codspeed-criterion-compat", features = ["async_tokio"] }
fs-err = { workspace = true }
jiff = { workspace = true }
reqwest = { workspace = true }
tokio = { workspace = true }
toml = { workspace = true }

[package.metadata.cargo-shear]
ignored = ["uv-extract"]
Expand Down
74 changes: 74 additions & 0 deletions crates/uv-bench/benches/airflow_run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! Benchmark `uv run python -V` in a warm cache against a fully synced airflow workspace.
//!
//! Lives in its own benchmark binary (separate process from `benches/uv.rs`) so that the
//! resolver benchmarks don't pollute the process state before [`uv::run`] is first invoked.

use std::hint::black_box;

use clap::Parser;
use criterion::{Criterion, criterion_group, criterion_main, measurement::WallTime};
use uv_cli::Cli;

#[expect(clippy::print_stderr)]
fn run_noop_airflow(c: &mut Criterion<WallTime>) {
let airflow_dir = std::path::absolute("../../airflow").unwrap();
if !airflow_dir.join("uv.lock").exists() {
let commit = "7fa400745ac7aebc7cc4ec21d3a047e9fb258310";
let repo = "https://github.com/apache/airflow.git";
eprintln!(
"Airflow checkout does not exist, not running benchmark.\n\
To set up:\n\
git init ../airflow\n\
git -C ../airflow remote add origin {repo}\n\
git -C ../airflow fetch --depth 1 origin {commit}\n\
git -C ../airflow checkout FETCH_HEAD"
);
return;
}
let cache_dir = std::path::absolute("../../.cache").unwrap();

let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();

let airflow_dir = airflow_dir.to_string_lossy().to_string();
let cache_dir = cache_dir.to_string_lossy().to_string();

// First call initializes `uv_flags`, logging, miette, etc. Subsequent calls reuse them.
let cli = Cli::try_parse_from([
"uv",
"run",
"--directory",
&airflow_dir,
"--cache-dir",
&cache_dir,
"--quiet",
"python",
"-V",
])
.unwrap();
runtime.block_on(uv::run(cli, true)).unwrap();

c.bench_function("run_noop_airflow", |b| {
b.iter(|| {
let cli = Cli::try_parse_from([
"uv",
"run",
"--directory",
black_box(&airflow_dir),
"--cache-dir",
black_box(&cache_dir),
"--quiet",
"--offline",
"python",
"-V",
])
.unwrap();
runtime.block_on(uv::run(cli, false)).unwrap();
});
});
}

criterion_group!(airflow_run, run_noop_airflow);
criterion_main!(airflow_run);
36 changes: 34 additions & 2 deletions crates/uv-bench/benches/uv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, RegistryClientBuilder};
use uv_distribution_types::Requirement;
use uv_python::PythonEnvironment;
use uv_resolver::Manifest;
use uv_resolver::{Lock, Manifest};

fn resolve_warm_jupyter(c: &mut Criterion<WallTime>) {
let manifest = Manifest::simple(vec![Requirement::from(
Expand Down Expand Up @@ -47,11 +47,43 @@ fn resolve_warm_airflow(c: &mut Criterion<WallTime>) {
// c.bench_function("resolve_warm_airflow_universal", |b| b.iter(&run));
// }

/// Benchmark lock file parsing for the airflow workspace.
fn parse_airflow_lockfile(c: &mut Criterion<WallTime>) {
let lockfile = airflow_lockfile();

c.bench_function("parse_airflow_lockfile", |b| {
b.iter(|| toml::from_str::<Lock>(black_box(&lockfile)).unwrap());
});
}

/// Fetch the airflow lockfile from GitHub, caching it locally.
fn airflow_lockfile() -> String {
let cache_path = std::path::absolute("../../.cache/airflow.uv.lock").unwrap();
if let Ok(contents) = fs_err::read_to_string(&cache_path) {
return contents;
}

let url = "https://raw.githubusercontent.com/apache/airflow/7fa400745ac7aebc7cc4ec21d3a047e9fb258310/uv.lock";
let error_message = "Failed to fetch airflow lockfile from GitHub";
let response = reqwest::blocking::get(url)
.expect(error_message)
.error_for_status()
.expect(error_message)
.text()
.expect(error_message);

fs_err::create_dir_all(cache_path.parent().unwrap()).unwrap();
fs_err::write(&cache_path, &response).unwrap();

response
}

criterion_group!(
uv,
resolve_warm_jupyter,
resolve_warm_jupyter_universal,
resolve_warm_airflow
resolve_warm_airflow,
parse_airflow_lockfile,
);
criterion_main!(uv);

Expand Down
1 change: 0 additions & 1 deletion crates/uv-distribution/src/metadata/dependency_groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ impl SourcedDependencyGroups {
} else {
MemberDiscovery::None
},
..DiscoveryOptions::default()
};

// The subsequent API takes an absolute path to the dir the pyproject is in
Expand Down
1 change: 0 additions & 1 deletion crates/uv-distribution/src/metadata/requires_dist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ impl RequiresDist {
} else {
MemberDiscovery::None
},
..DiscoveryOptions::default()
};
let Some(project_workspace) =
ProjectWorkspace::from_maybe_project_root(install_path, &discovery, cache).await?
Expand Down
5 changes: 2 additions & 3 deletions crates/uv-workspace/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
pub use workspace::{
DiscoveryOptions, Editability, MemberDiscovery, ProjectDiscovery, ProjectWorkspace,
RequiresPythonSources, VirtualProject, Workspace, WorkspaceCache, WorkspaceError,
WorkspaceMember,
DiscoveryOptions, Editability, MemberDiscovery, ProjectWorkspace, RequiresPythonSources,
VirtualProject, Workspace, WorkspaceCache, WorkspaceError, WorkspaceMember,
};

pub mod dependency_groups;
Expand Down
Loading
Loading