mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Add a BENCHMARKS.md
with rendered benchmarks (#1211)
As a precursor to the release, I want to include a structured document with detailed benchmarks. Closes https://github.com/astral-sh/puffin/issues/1210.
This commit is contained in:
parent
b9d89e7624
commit
c4bfb6efee
6 changed files with 631 additions and 1 deletions
|
@ -36,6 +36,8 @@ puffin-resolver = { path = "../puffin-resolver" }
|
|||
pypi-types = { path = "../pypi-types" }
|
||||
puffin-traits = { path = "../puffin-traits" }
|
||||
|
||||
# Any dependencies that are exclusively used in `puffin-dev` should be listed as non-workspace
|
||||
# dependencies, to ensure that we're forced to think twice before including them in other crates.
|
||||
anstream = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
|
@ -46,7 +48,12 @@ indicatif = { workspace = true }
|
|||
itertools = { workspace = true }
|
||||
owo-colors = { workspace = true }
|
||||
petgraph = { workspace = true }
|
||||
poloto = { version = "19.1.2" }
|
||||
resvg = { version = "0.29.0" }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tagu = { version = "0.1.6" }
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
|
|
@ -22,6 +22,7 @@ use resolve_many::ResolveManyArgs;
|
|||
|
||||
use crate::build::{build, BuildArgs};
|
||||
use crate::install_many::InstallManyArgs;
|
||||
use crate::render_benchmarks::RenderBenchmarksArgs;
|
||||
use crate::resolve_cli::ResolveCliArgs;
|
||||
use crate::wheel_metadata::WheelMetadataArgs;
|
||||
|
||||
|
@ -43,6 +44,7 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
|||
|
||||
mod build;
|
||||
mod install_many;
|
||||
mod render_benchmarks;
|
||||
mod resolve_cli;
|
||||
mod resolve_many;
|
||||
mod wheel_metadata;
|
||||
|
@ -66,6 +68,7 @@ enum Cli {
|
|||
/// Resolve requirements passed on the CLI
|
||||
Resolve(ResolveCliArgs),
|
||||
WheelMetadata(WheelMetadataArgs),
|
||||
RenderBenchmarks(RenderBenchmarksArgs),
|
||||
}
|
||||
|
||||
#[instrument] // Anchor span to check for overhead
|
||||
|
@ -86,6 +89,7 @@ async fn run() -> Result<()> {
|
|||
resolve_cli::resolve_cli(args).await?;
|
||||
}
|
||||
Cli::WheelMetadata(args) => wheel_metadata::wheel_metadata(args).await?,
|
||||
Cli::RenderBenchmarks(args) => render_benchmarks::render_benchmarks(&args)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
113
crates/puffin-dev/src/render_benchmarks.rs
Normal file
113
crates/puffin-dev/src/render_benchmarks.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::Parser;
|
||||
use poloto::build;
|
||||
use resvg::usvg_text_layout::{fontdb, TreeTextToPath};
|
||||
use serde::Deserialize;
|
||||
use tagu::prelude::*;
|
||||
|
||||
#[derive(Parser)]
|
||||
pub(crate) struct RenderBenchmarksArgs {
|
||||
/// Path to a JSON output from a `hyperfine` benchmark.
|
||||
path: PathBuf,
|
||||
/// Title of the plot.
|
||||
#[clap(long, short)]
|
||||
title: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) fn render_benchmarks(args: &RenderBenchmarksArgs) -> Result<()> {
|
||||
let mut results: BenchmarkResults = serde_json::from_slice(&std::fs::read(&args.path)?)?;
|
||||
|
||||
// Replace the command with a shorter name. (The command typically includes the benchmark name,
|
||||
// but we assume we're running over a single benchmark here.)
|
||||
for result in &mut results.results {
|
||||
if result.command.starts_with("puffin") {
|
||||
result.command = "puffin".into();
|
||||
} else if result.command.starts_with("pip-compile") {
|
||||
result.command = "pip-compile".into();
|
||||
} else if result.command.starts_with("pip-sync") {
|
||||
result.command = "pip-sync".into();
|
||||
} else if result.command.starts_with("poetry") {
|
||||
result.command = "poetry".into();
|
||||
} else {
|
||||
return Err(anyhow!("unknown command: {}", result.command));
|
||||
}
|
||||
}
|
||||
|
||||
let fontdb = load_fonts();
|
||||
|
||||
render_to_png(
|
||||
&plot_benchmark(args.title.as_deref().unwrap_or("Benchmark"), &results)?,
|
||||
&args.path.with_extension("png"),
|
||||
&fontdb,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render a benchmark to an SVG (as a string).
|
||||
fn plot_benchmark(heading: &str, results: &BenchmarkResults) -> Result<String> {
|
||||
let mut data = Vec::new();
|
||||
for result in &results.results {
|
||||
data.push((result.mean, &result.command));
|
||||
}
|
||||
|
||||
let theme = poloto::render::Theme::light();
|
||||
let theme = theme.append(tagu::build::raw(
|
||||
".poloto0.poloto_fill{fill: #6340AC !important;}",
|
||||
));
|
||||
let theme = theme.append(tagu::build::raw(
|
||||
".poloto_background{fill: white !important;}",
|
||||
));
|
||||
|
||||
Ok(build::bar::gen_simple("", data, [0.0])
|
||||
.label((heading, "Time (s)", ""))
|
||||
.append_to(poloto::header().append(theme))
|
||||
.render_string()?)
|
||||
}
|
||||
|
||||
/// Render an SVG to a PNG file.
|
||||
fn render_to_png(data: &str, path: &Path, fontdb: &fontdb::Database) -> Result<()> {
|
||||
let mut tree = resvg::usvg::Tree::from_str(data, &resvg::usvg::Options::default())?;
|
||||
tree.convert_text(fontdb);
|
||||
let fit_to = resvg::usvg::FitTo::Width(1600);
|
||||
let size = fit_to
|
||||
.fit_to(tree.size.to_screen_size())
|
||||
.ok_or_else(|| anyhow!("failed to fit to screen size"))?;
|
||||
let mut pixmap = resvg::tiny_skia::Pixmap::new(size.width(), size.height()).unwrap();
|
||||
resvg::render(
|
||||
&tree,
|
||||
fit_to,
|
||||
resvg::tiny_skia::Transform::default(),
|
||||
pixmap.as_mut(),
|
||||
)
|
||||
.ok_or_else(|| anyhow!("failed to render"))?;
|
||||
std::fs::create_dir_all(path.parent().unwrap())?;
|
||||
pixmap.save_png(path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load the system fonts and set the default font families.
|
||||
fn load_fonts() -> fontdb::Database {
|
||||
let mut fontdb = fontdb::Database::new();
|
||||
fontdb.load_system_fonts();
|
||||
fontdb.set_serif_family("Times New Roman");
|
||||
fontdb.set_sans_serif_family("Arial");
|
||||
fontdb.set_cursive_family("Comic Sans MS");
|
||||
fontdb.set_fantasy_family("Impact");
|
||||
fontdb.set_monospace_family("Courier New");
|
||||
|
||||
fontdb
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct BenchmarkResults {
|
||||
results: Vec<BenchmarkResult>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct BenchmarkResult {
|
||||
command: String,
|
||||
mean: f64,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue