mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-03 05:03:46 +00:00
Use FxHasher in resolver (#3641)
## Summary We can use `FxHasher` in a few more places for string and version keys. This gives a consistent ~2% improvement to warm resolves.
This commit is contained in:
parent
39af09f09b
commit
0f67a6ceea
5 changed files with 51 additions and 30 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::hash::Hash;
|
use std::hash::{BuildHasher, Hash, RandomState};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
|
|
@ -14,11 +14,11 @@ use tokio::sync::Notify;
|
||||||
///
|
///
|
||||||
/// Note that this always clones the value out of the underlying map. Because
|
/// Note that this always clones the value out of the underlying map. Because
|
||||||
/// of this, it's common to wrap the `V` in an `Arc<V>` to make cloning cheap.
|
/// of this, it's common to wrap the `V` in an `Arc<V>` to make cloning cheap.
|
||||||
pub struct OnceMap<K, V> {
|
pub struct OnceMap<K, V, H = RandomState> {
|
||||||
items: DashMap<K, Value<V>>,
|
items: DashMap<K, Value<V>, H>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Eq + Hash, V: Clone> OnceMap<K, V> {
|
impl<K: Eq + Hash, V: Clone, H: BuildHasher + Clone> OnceMap<K, V, H> {
|
||||||
/// Register that you want to start a job.
|
/// Register that you want to start a job.
|
||||||
///
|
///
|
||||||
/// If this method returns `true`, you need to start a job and call [`OnceMap::done`] eventually
|
/// If this method returns `true`, you need to start a job and call [`OnceMap::done`] eventually
|
||||||
|
|
@ -97,10 +97,10 @@ impl<K: Eq + Hash, V: Clone> OnceMap<K, V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Eq + Hash + Clone, V> Default for OnceMap<K, V> {
|
impl<K: Eq + Hash + Clone, V, H: Default + BuildHasher + Clone> Default for OnceMap<K, V, H> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
items: DashMap::new(),
|
items: DashMap::with_hasher(H::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,10 @@ use std::sync::Arc;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use pubgrub::range::Range;
|
use pubgrub::range::Range;
|
||||||
use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter};
|
use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use dashmap::{DashMap, DashSet};
|
use dashmap::DashMap;
|
||||||
use distribution_types::{BuiltDist, IndexLocations, InstalledDist, ParsedUrlError, SourceDist};
|
use distribution_types::{BuiltDist, IndexLocations, InstalledDist, ParsedUrlError, SourceDist};
|
||||||
use once_map::OnceMap;
|
|
||||||
use pep440_rs::Version;
|
use pep440_rs::Version;
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::Requirement;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
@ -19,7 +18,9 @@ use crate::candidate_selector::CandidateSelector;
|
||||||
use crate::dependency_provider::UvDependencyProvider;
|
use crate::dependency_provider::UvDependencyProvider;
|
||||||
use crate::pubgrub::{PubGrubPackage, PubGrubPython, PubGrubReportFormatter};
|
use crate::pubgrub::{PubGrubPackage, PubGrubPython, PubGrubReportFormatter};
|
||||||
use crate::python_requirement::PythonRequirement;
|
use crate::python_requirement::PythonRequirement;
|
||||||
use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason, VersionsResponse};
|
use crate::resolver::{
|
||||||
|
FxOnceMap, IncompletePackage, UnavailablePackage, UnavailableReason, VersionsResponse,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ResolveError {
|
pub enum ResolveError {
|
||||||
|
|
@ -235,8 +236,8 @@ impl NoSolutionError {
|
||||||
pub(crate) fn with_available_versions(
|
pub(crate) fn with_available_versions(
|
||||||
mut self,
|
mut self,
|
||||||
python_requirement: &PythonRequirement,
|
python_requirement: &PythonRequirement,
|
||||||
visited: &DashSet<PackageName>,
|
visited: &FxHashSet<PackageName>,
|
||||||
package_versions: &OnceMap<PackageName, Arc<VersionsResponse>>,
|
package_versions: &FxOnceMap<PackageName, Arc<VersionsResponse>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut available_versions = IndexMap::default();
|
let mut available_versions = IndexMap::default();
|
||||||
for package in self.derivation_tree.packages() {
|
for package in self.derivation_tree.packages() {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ use distribution_types::{
|
||||||
Dist, DistributionMetadata, Name, ParsedUrlError, Requirement, ResolvedDist, VersionId,
|
Dist, DistributionMetadata, Name, ParsedUrlError, Requirement, ResolvedDist, VersionId,
|
||||||
VersionOrUrlRef,
|
VersionOrUrlRef,
|
||||||
};
|
};
|
||||||
use once_map::OnceMap;
|
|
||||||
use pep440_rs::{Version, VersionSpecifier};
|
use pep440_rs::{Version, VersionSpecifier};
|
||||||
use pep508_rs::MarkerEnvironment;
|
use pep508_rs::MarkerEnvironment;
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
|
|
@ -22,6 +21,7 @@ use crate::preferences::Preferences;
|
||||||
use crate::pubgrub::{PubGrubDistribution, PubGrubPackage};
|
use crate::pubgrub::{PubGrubDistribution, PubGrubPackage};
|
||||||
use crate::redirect::url_to_precise;
|
use crate::redirect::url_to_precise;
|
||||||
use crate::resolution::AnnotatedDist;
|
use crate::resolution::AnnotatedDist;
|
||||||
|
use crate::resolver::FxOnceMap;
|
||||||
use crate::{
|
use crate::{
|
||||||
lock, InMemoryIndex, Lock, LockError, Manifest, MetadataResponse, ResolveError,
|
lock, InMemoryIndex, Lock, LockError, Manifest, MetadataResponse, ResolveError,
|
||||||
VersionsResponse,
|
VersionsResponse,
|
||||||
|
|
@ -45,8 +45,8 @@ impl ResolutionGraph {
|
||||||
pub(crate) fn from_state(
|
pub(crate) fn from_state(
|
||||||
selection: &SelectedDependencies<UvDependencyProvider>,
|
selection: &SelectedDependencies<UvDependencyProvider>,
|
||||||
pins: &FilePins,
|
pins: &FilePins,
|
||||||
packages: &OnceMap<PackageName, Arc<VersionsResponse>>,
|
packages: &FxOnceMap<PackageName, Arc<VersionsResponse>>,
|
||||||
distributions: &OnceMap<VersionId, Arc<MetadataResponse>>,
|
distributions: &FxOnceMap<VersionId, Arc<MetadataResponse>>,
|
||||||
state: &State<UvDependencyProvider>,
|
state: &State<UvDependencyProvider>,
|
||||||
preferences: &Preferences,
|
preferences: &Preferences,
|
||||||
editables: Editables,
|
editables: Editables,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
use std::hash::BuildHasherDefault;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use distribution_types::VersionId;
|
use distribution_types::VersionId;
|
||||||
use once_map::OnceMap;
|
use once_map::OnceMap;
|
||||||
|
use rustc_hash::FxHasher;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
use crate::resolver::provider::{MetadataResponse, VersionsResponse};
|
use crate::resolver::provider::{MetadataResponse, VersionsResponse};
|
||||||
|
|
@ -14,20 +16,22 @@ pub struct InMemoryIndex(Arc<SharedInMemoryIndex>);
|
||||||
struct SharedInMemoryIndex {
|
struct SharedInMemoryIndex {
|
||||||
/// A map from package name to the metadata for that package and the index where the metadata
|
/// A map from package name to the metadata for that package and the index where the metadata
|
||||||
/// came from.
|
/// came from.
|
||||||
packages: OnceMap<PackageName, Arc<VersionsResponse>>,
|
packages: FxOnceMap<PackageName, Arc<VersionsResponse>>,
|
||||||
|
|
||||||
/// A map from package ID to metadata for that distribution.
|
/// A map from package ID to metadata for that distribution.
|
||||||
distributions: OnceMap<VersionId, Arc<MetadataResponse>>,
|
distributions: FxOnceMap<VersionId, Arc<MetadataResponse>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) type FxOnceMap<K, V> = OnceMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||||
|
|
||||||
impl InMemoryIndex {
|
impl InMemoryIndex {
|
||||||
/// Returns a reference to the package metadata map.
|
/// Returns a reference to the package metadata map.
|
||||||
pub fn packages(&self) -> &OnceMap<PackageName, Arc<VersionsResponse>> {
|
pub fn packages(&self) -> &FxOnceMap<PackageName, Arc<VersionsResponse>> {
|
||||||
&self.0.packages
|
&self.0.packages
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the distribution metadata map.
|
/// Returns a reference to the distribution metadata map.
|
||||||
pub fn distributions(&self) -> &OnceMap<VersionId, Arc<MetadataResponse>> {
|
pub fn distributions(&self) -> &FxOnceMap<VersionId, Arc<MetadataResponse>> {
|
||||||
&self.0.distributions
|
&self.0.distributions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use dashmap::{DashMap, DashSet};
|
use dashmap::DashMap;
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt, TryFutureExt};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use pubgrub::error::PubGrubError;
|
use pubgrub::error::PubGrubError;
|
||||||
use pubgrub::range::Range;
|
use pubgrub::range::Range;
|
||||||
|
|
@ -49,6 +49,7 @@ use crate::pubgrub::{
|
||||||
use crate::python_requirement::PythonRequirement;
|
use crate::python_requirement::PythonRequirement;
|
||||||
use crate::resolution::ResolutionGraph;
|
use crate::resolution::ResolutionGraph;
|
||||||
use crate::resolver::batch_prefetch::BatchPrefetcher;
|
use crate::resolver::batch_prefetch::BatchPrefetcher;
|
||||||
|
pub(crate) use crate::resolver::index::FxOnceMap;
|
||||||
pub use crate::resolver::index::InMemoryIndex;
|
pub use crate::resolver::index::InMemoryIndex;
|
||||||
pub use crate::resolver::provider::{
|
pub use crate::resolver::provider::{
|
||||||
DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
|
DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
|
||||||
|
|
@ -202,8 +203,6 @@ struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
|
||||||
unavailable_packages: DashMap<PackageName, UnavailablePackage>,
|
unavailable_packages: DashMap<PackageName, UnavailablePackage>,
|
||||||
/// Incompatibilities for packages that are unavailable at specific versions.
|
/// Incompatibilities for packages that are unavailable at specific versions.
|
||||||
incomplete_packages: DashMap<PackageName, DashMap<Version, IncompletePackage>>,
|
incomplete_packages: DashMap<PackageName, DashMap<Version, IncompletePackage>>,
|
||||||
/// The set of all registry-based packages visited during resolution.
|
|
||||||
visited: DashSet<PackageName>,
|
|
||||||
reporter: Option<Arc<dyn Reporter>>,
|
reporter: Option<Arc<dyn Reporter>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,7 +285,6 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
||||||
index: index.clone(),
|
index: index.clone(),
|
||||||
unavailable_packages: DashMap::default(),
|
unavailable_packages: DashMap::default(),
|
||||||
incomplete_packages: DashMap::default(),
|
incomplete_packages: DashMap::default(),
|
||||||
visited: DashSet::default(),
|
|
||||||
selector: CandidateSelector::for_resolution(options, &manifest, markers),
|
selector: CandidateSelector::for_resolution(options, &manifest, markers),
|
||||||
dependency_mode: options.dependency_mode,
|
dependency_mode: options.dependency_mode,
|
||||||
urls: Urls::from_manifest(&manifest, markers, options.dependency_mode)?,
|
urls: Urls::from_manifest(&manifest, markers, options.dependency_mode)?,
|
||||||
|
|
@ -332,7 +330,11 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
||||||
let (request_sink, request_stream) = mpsc::channel(300);
|
let (request_sink, request_stream) = mpsc::channel(300);
|
||||||
|
|
||||||
// Run the fetcher.
|
// Run the fetcher.
|
||||||
let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
|
let requests_fut = state
|
||||||
|
.clone()
|
||||||
|
.fetch(provider.clone(), request_stream)
|
||||||
|
.map_err(|err| (err, FxHashSet::default()))
|
||||||
|
.fuse();
|
||||||
|
|
||||||
// Spawn the PubGrub solver on a dedicated thread.
|
// Spawn the PubGrub solver on a dedicated thread.
|
||||||
let solver = state.clone();
|
let solver = state.clone();
|
||||||
|
|
@ -347,7 +349,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
||||||
|
|
||||||
let resolve_fut = async move {
|
let resolve_fut = async move {
|
||||||
rx.await
|
rx.await
|
||||||
.map_err(|_| ResolveError::ChannelClosed)
|
.map_err(|_| (ResolveError::ChannelClosed, FxHashSet::default()))
|
||||||
.and_then(|result| result)
|
.and_then(|result| result)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -357,13 +359,13 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
||||||
state.on_complete();
|
state.on_complete();
|
||||||
Ok(resolution)
|
Ok(resolution)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err((err, visited)) => {
|
||||||
// Add version information to improve unsat error messages.
|
// Add version information to improve unsat error messages.
|
||||||
Err(if let ResolveError::NoSolution(err) = err {
|
Err(if let ResolveError::NoSolution(err) = err {
|
||||||
ResolveError::NoSolution(
|
ResolveError::NoSolution(
|
||||||
err.with_available_versions(
|
err.with_available_versions(
|
||||||
&state.python_requirement,
|
&state.python_requirement,
|
||||||
&state.visited,
|
&visited,
|
||||||
state.index.packages(),
|
state.index.packages(),
|
||||||
)
|
)
|
||||||
.with_selector(state.selector.clone())
|
.with_selector(state.selector.clone())
|
||||||
|
|
@ -381,11 +383,23 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
|
impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
|
||||||
/// Run the PubGrub solver.
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
fn solve(
|
fn solve(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
request_sink: Sender<Request>,
|
request_sink: Sender<Request>,
|
||||||
|
) -> Result<ResolutionGraph, (ResolveError, FxHashSet<PackageName>)> {
|
||||||
|
let mut visited = FxHashSet::default();
|
||||||
|
self.solve_tracked(&mut visited, request_sink)
|
||||||
|
.map_err(|err| (err, visited))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the PubGrub solver, updating the `visited` set for each package visited during
|
||||||
|
/// resolution.
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
fn solve_tracked(
|
||||||
|
self: Arc<Self>,
|
||||||
|
visited: &mut FxHashSet<PackageName>,
|
||||||
|
request_sink: Sender<Request>,
|
||||||
) -> Result<ResolutionGraph, ResolveError> {
|
) -> Result<ResolutionGraph, ResolveError> {
|
||||||
let root = PubGrubPackage::Root(self.project.clone());
|
let root = PubGrubPackage::Root(self.project.clone());
|
||||||
let mut prefetcher = BatchPrefetcher::default();
|
let mut prefetcher = BatchPrefetcher::default();
|
||||||
|
|
@ -450,6 +464,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
&state.next,
|
&state.next,
|
||||||
term_intersection.unwrap_positive(),
|
term_intersection.unwrap_positive(),
|
||||||
&mut state.pins,
|
&mut state.pins,
|
||||||
|
visited,
|
||||||
&request_sink,
|
&request_sink,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
@ -681,6 +696,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
package: &PubGrubPackage,
|
package: &PubGrubPackage,
|
||||||
range: &Range<Version>,
|
range: &Range<Version>,
|
||||||
pins: &mut FilePins,
|
pins: &mut FilePins,
|
||||||
|
visited: &mut FxHashSet<PackageName>,
|
||||||
request_sink: &Sender<Request>,
|
request_sink: &Sender<Request>,
|
||||||
) -> Result<Option<ResolverVersion>, ResolveError> {
|
) -> Result<Option<ResolverVersion>, ResolveError> {
|
||||||
match package {
|
match package {
|
||||||
|
|
@ -805,7 +821,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
.packages()
|
.packages()
|
||||||
.wait_blocking(package_name)
|
.wait_blocking(package_name)
|
||||||
.ok_or(ResolveError::Unregistered)?;
|
.ok_or(ResolveError::Unregistered)?;
|
||||||
self.visited.insert(package_name.clone());
|
visited.insert(package_name.clone());
|
||||||
|
|
||||||
let version_maps = match *versions_response {
|
let version_maps = match *versions_response {
|
||||||
VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
|
VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue