diff --git a/Cargo.lock b/Cargo.lock index ce0be253b..7bbaece65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -900,6 +900,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1315,6 +1324,7 @@ dependencies = [ "data-encoding", "fs-err", "fs2", + "fxhash", "glibc_version", "goblin", "indoc 2.0.4", @@ -2155,6 +2165,7 @@ dependencies = [ "bitflags 2.4.1", "colored", "futures", + "fxhash", "insta", "once_cell", "pep440_rs 0.3.12", diff --git a/Cargo.toml b/Cargo.toml index ff200c6d0..a92b66766 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ flate2 = { version = "1.0.28" } fs-err = { version = "2.9.0" } fs2 = { version = "0.4.3" } futures = { version = "0.3.28" } +fxhash = { version = "0.2.1" } glibc_version = { version = "0.1.2" } goblin = { version = "0.7.1" } http-cache-reqwest = { version = "0.11.3" } @@ -38,6 +39,7 @@ mailparse = { version = "0.14.0" } memchr = { version = "2.6.4" } miette = { version = "5.10.0" } once_cell = { version = "1.18.0" } +petgraph = { version = "0.6.4" } platform-info = { version = "2.0.2" } plist = { version = "1.5.0" } pyproject-toml = { version = "0.7.0" } diff --git a/crates/install-wheel-rs/Cargo.toml b/crates/install-wheel-rs/Cargo.toml index 3d284705b..9b77e61f6 100644 --- a/crates/install-wheel-rs/Cargo.toml +++ b/crates/install-wheel-rs/Cargo.toml @@ -26,6 +26,7 @@ csv = { workspace = true } data-encoding = { workspace = true } fs-err = { workspace = true } fs2 = { workspace = true } +fxhash = { workspace = true } glibc_version = { workspace = true } goblin = { workspace = true } mailparse = { workspace = true } diff --git a/crates/install-wheel-rs/src/lib.rs b/crates/install-wheel-rs/src/lib.rs index 0190bd84a..a00d49df1 100644 --- a/crates/install-wheel-rs/src/lib.rs +++ b/crates/install-wheel-rs/src/lib.rs @@ -31,9 +31,6 @@ mod wheel; pub enum Error { #[error(transparent)] IO(#[from] io::Error), - /// This shouldn't actually be possible to occur - #[error("Failed to serialize direct_url.json ಠ_ಠ")] - DirectUrlSerdeJson(#[source] serde_json::Error), /// Tags/metadata didn't match platform #[error("The wheel is incompatible with the current platform {os} {arch}")] IncompatibleWheel { os: Os, arch: Arch }, diff --git a/crates/install-wheel-rs/src/script.rs b/crates/install-wheel-rs/src/script.rs index cc6f10953..6f9489f50 100644 --- a/crates/install-wheel-rs/src/script.rs +++ b/crates/install-wheel-rs/src/script.rs @@ -1,21 +1,9 @@ -use std::collections::{HashMap, HashSet}; - +use fxhash::FxHashSet; use regex::Regex; use serde::Serialize; use crate::Error; -/// Minimal `direct_url.json` schema -/// -/// -/// -#[derive(Serialize)] -struct DirectUrl { - #[allow(clippy::zero_sized_map_values)] - archive_info: HashMap<(), ()>, - url: String, -} - /// A script defining the name of the runnable entrypoint and the module and function that should be /// run. #[cfg(feature = "python_bindings")] @@ -57,12 +45,12 @@ impl Script { .captures(value) .ok_or_else(|| Error::InvalidWheel(format!("invalid console script: '{value}'")))?; if let Some(script_extras) = captures.name("extras") { - let script_extras = script_extras - .as_str() - .split(',') - .map(|extra| extra.trim().to_string()) - .collect::>(); if let Some(extras) = extras { + let script_extras = script_extras + .as_str() + .split(',') + .map(|extra| extra.trim().to_string()) + .collect::>(); if !script_extras.is_subset(&extras.iter().cloned().collect()) { return Ok(None); } diff --git a/crates/install-wheel-rs/src/wheel.rs b/crates/install-wheel-rs/src/wheel.rs index 03e2dd3e7..4e39ace76 100644 --- a/crates/install-wheel-rs/src/wheel.rs +++ b/crates/install-wheel-rs/src/wheel.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::ffi::OsString; use std::io::{BufRead, BufReader, BufWriter, Cursor, Read, Seek, Write}; use std::path::{Path, PathBuf}; @@ -9,6 +9,7 @@ use configparser::ini::Ini; use data_encoding::BASE64URL_NOPAD; use fs_err as fs; use fs_err::{DirEntry, File}; +use fxhash::{FxHashMap, FxHashSet}; use mailparse::MailHeaderMap; use sha2::{Digest, Sha256}; use tempfile::tempdir; @@ -158,7 +159,7 @@ fn unpack_wheel_files( // Cache the created parent dirs to avoid io calls // When deactivating bytecode compilation and sha2 those were 5% of total runtime, with // cache it 2.3% - let mut created_dirs = HashSet::new(); + let mut created_dirs = FxHashSet::default(); // https://github.com/zip-rs/zip/blob/7edf2489d5cff8b80f02ee6fc5febf3efd0a9442/examples/extract.rs for i in 0..archive.len() { let mut file = archive @@ -858,8 +859,8 @@ pub fn read_record_file(record: &mut impl Read) -> Result, Erro pub fn parse_key_value_file( file: &mut impl Read, debug_filename: &str, -) -> Result>, Error> { - let mut data: HashMap> = HashMap::new(); +) -> Result>, Error> { + let mut data: FxHashMap> = FxHashMap::default(); let file = BufReader::new(file); for (line_no, line) in file.lines().enumerate() { diff --git a/crates/puffin-resolver/Cargo.toml b/crates/puffin-resolver/Cargo.toml index b189d379e..cc5b8616d 100644 --- a/crates/puffin-resolver/Cargo.toml +++ b/crates/puffin-resolver/Cargo.toml @@ -23,12 +23,13 @@ anyhow = { workspace = true } bitflags = { workspace = true } colored = { workspace = true } futures = { workspace = true } +fxhash = { workspace = true } once_cell = { workspace = true } +petgraph = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } waitmap = { workspace = true } -petgraph = "0.6.4" [dev-dependencies] once_cell = { version = "1.18.0" } diff --git a/crates/puffin-resolver/src/resolution.rs b/crates/puffin-resolver/src/resolution.rs index 1d38e2a81..1fb44745a 100644 --- a/crates/puffin-resolver/src/resolution.rs +++ b/crates/puffin-resolver/src/resolution.rs @@ -1,6 +1,7 @@ -use std::collections::{BTreeMap, HashMap}; +use std::hash::BuildHasherDefault; use colored::Colorize; +use fxhash::FxHashMap; use petgraph::visit::EdgeRef; use pubgrub::range::Range; use pubgrub::solver::{Kind, State}; @@ -49,11 +50,11 @@ impl PinnedPackage { /// A set of packages pinned at specific versions. #[derive(Debug, Default)] -pub struct Resolution(BTreeMap); +pub struct Resolution(FxHashMap); impl Resolution { /// Create a new resolution from the given pinned packages. - pub(crate) fn new(packages: BTreeMap) -> Self { + pub(crate) fn new(packages: FxHashMap) -> Self { Self(packages) } @@ -87,7 +88,7 @@ impl Graph { /// Create a new graph from the resolved `PubGrub` state. pub fn from_state( selection: &SelectedDependencies, - pins: &HashMap>, + pins: &FxHashMap>, state: &State>, ) -> Self { // TODO(charlie): petgraph is a really heavy and unnecessary dependency here. We should @@ -95,7 +96,8 @@ impl Graph { let mut graph = petgraph::graph::Graph::with_capacity(selection.len(), selection.len()); // Add every package to the graph. - let mut inverse = HashMap::with_capacity(selection.len()); + let mut inverse = + FxHashMap::with_capacity_and_hasher(selection.len(), BuildHasherDefault::default()); for (package, version) in selection { let PubGrubPackage::Package(package_name, None) = package else { continue; diff --git a/crates/puffin-resolver/src/resolver.rs b/crates/puffin-resolver/src/resolver.rs index 23e2328d3..03e917759 100644 --- a/crates/puffin-resolver/src/resolver.rs +++ b/crates/puffin-resolver/src/resolver.rs @@ -2,7 +2,6 @@ use std::borrow::Borrow; use std::collections::hash_map::Entry; -use std::collections::{HashMap, HashSet}; use std::str::FromStr; use std::sync::Arc; @@ -10,6 +9,7 @@ use anyhow::Result; use futures::channel::mpsc::UnboundedReceiver; use futures::future::Either; use futures::{pin_mut, FutureExt, StreamExt, TryFutureExt}; +use fxhash::{FxHashMap, FxHashSet}; use pubgrub::error::PubGrubError; use pubgrub::range::Range; use pubgrub::solver::{Incompatibility, State}; @@ -104,14 +104,14 @@ impl<'a> Resolver<'a> { let root = PubGrubPackage::Root; // Keep track of the packages for which we've requested metadata. - let mut requested_packages = HashSet::new(); - let mut requested_versions = HashSet::new(); - let mut pins = HashMap::new(); + let mut requested_packages = FxHashSet::default(); + let mut requested_versions = FxHashSet::default(); + let mut pins = FxHashMap::default(); // Start the solve. let mut state = State::init(root.clone(), MIN_VERSION.clone()); - let mut added_dependencies: HashMap> = - HashMap::default(); + let mut added_dependencies: FxHashMap> = + FxHashMap::default(); let mut next = root; loop { @@ -243,8 +243,8 @@ impl<'a> Resolver<'a> { async fn choose_package_version, U: Borrow>>( &self, mut potential_packages: Vec<(T, U)>, - pins: &mut HashMap>, - in_flight: &mut HashSet, + pins: &mut FxHashMap>, + in_flight: &mut FxHashSet, request_sink: &futures::channel::mpsc::UnboundedSender, ) -> Result<(T, Option), ResolveError> { let mut selection = 0usize; @@ -373,8 +373,8 @@ impl<'a> Resolver<'a> { &self, package: &PubGrubPackage, version: &PubGrubVersion, - pins: &mut HashMap>, - requested_packages: &mut HashSet, + pins: &mut FxHashMap>, + requested_packages: &mut FxHashSet, request_sink: &futures::channel::mpsc::UnboundedSender, ) -> Result { match package { diff --git a/crates/puffin-resolver/src/wheel_finder.rs b/crates/puffin-resolver/src/wheel_finder.rs index 098e0d10b..bcc255f4a 100644 --- a/crates/puffin-resolver/src/wheel_finder.rs +++ b/crates/puffin-resolver/src/wheel_finder.rs @@ -2,12 +2,13 @@ //! //! This is similar to running `pip install` with the `--no-deps` flag. -use std::collections::BTreeMap; +use std::hash::BuildHasherDefault; use std::str::FromStr; use anyhow::Result; use futures::future::Either; use futures::{StreamExt, TryFutureExt}; +use fxhash::FxHashMap; use tracing::debug; use pep440_rs::Version; @@ -81,7 +82,8 @@ impl<'a> WheelFinder<'a> { } // Resolve the requirements. - let mut resolution: BTreeMap = BTreeMap::new(); + let mut resolution: FxHashMap = + FxHashMap::with_capacity_and_hasher(requirements.len(), BuildHasherDefault::default()); while let Some(chunk) = package_stream.next().await { for result in chunk {