mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
perf: more efficient deno cache
and npm package info usage (#16592)
1. There was a lot of cloning going on with `NpmPackageInfo`. This is now stored in an `Arc<NpmPackageInfo>` and cloning only happens on the individual version. 2. The package cache is now cleared from memory after resolution. 3. This surfaced a bug in `deno cache` and I noticed it can be more efficient if we have multiple root specifiers if we provide all the specifiers as roots.
This commit is contained in:
parent
7f0546a6b7
commit
8dc242f789
6 changed files with 84 additions and 59 deletions
28
cli/main.rs
28
cli/main.rs
|
@ -284,23 +284,23 @@ async fn check_command(
|
||||||
|
|
||||||
async fn load_and_type_check(
|
async fn load_and_type_check(
|
||||||
ps: &ProcState,
|
ps: &ProcState,
|
||||||
files: &Vec<String>,
|
files: &[String],
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
let lib = ps.options.ts_type_lib_window();
|
let lib = ps.options.ts_type_lib_window();
|
||||||
|
|
||||||
for file in files {
|
let specifiers = files
|
||||||
let specifier = resolve_url_or_path(file)?;
|
.iter()
|
||||||
|
.map(|file| resolve_url_or_path(file))
|
||||||
ps.prepare_module_load(
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
vec![specifier],
|
ps.prepare_module_load(
|
||||||
false,
|
specifiers,
|
||||||
lib,
|
false,
|
||||||
Permissions::allow_all(),
|
lib,
|
||||||
Permissions::allow_all(),
|
Permissions::allow_all(),
|
||||||
false,
|
Permissions::allow_all(),
|
||||||
)
|
false,
|
||||||
.await?;
|
)
|
||||||
}
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,12 +185,12 @@ pub trait NpmRegistryApi: Clone + Sync + Send + 'static {
|
||||||
fn maybe_package_info(
|
fn maybe_package_info(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> BoxFuture<'static, Result<Option<NpmPackageInfo>, AnyError>>;
|
) -> BoxFuture<'static, Result<Option<Arc<NpmPackageInfo>>, AnyError>>;
|
||||||
|
|
||||||
fn package_info(
|
fn package_info(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> BoxFuture<'static, Result<NpmPackageInfo, AnyError>> {
|
) -> BoxFuture<'static, Result<Arc<NpmPackageInfo>, AnyError>> {
|
||||||
let api = self.clone();
|
let api = self.clone();
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
async move {
|
async move {
|
||||||
|
@ -212,13 +212,14 @@ pub trait NpmRegistryApi: Clone + Sync + Send + 'static {
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
let version = version.to_string();
|
let version = version.to_string();
|
||||||
async move {
|
async move {
|
||||||
// todo(dsherret): this could be optimized to not clone the
|
let package_info = api.package_info(&name).await?;
|
||||||
// entire package info in the case of the RealNpmRegistryApi
|
Ok(package_info.versions.get(&version).cloned())
|
||||||
let mut package_info = api.package_info(&name).await?;
|
|
||||||
Ok(package_info.versions.remove(&version))
|
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clears the internal memory cache.
|
||||||
|
fn clear_memory_cache(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -268,17 +269,21 @@ impl NpmRegistryApi for RealNpmRegistryApi {
|
||||||
fn maybe_package_info(
|
fn maybe_package_info(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> BoxFuture<'static, Result<Option<NpmPackageInfo>, AnyError>> {
|
) -> BoxFuture<'static, Result<Option<Arc<NpmPackageInfo>>, AnyError>> {
|
||||||
let api = self.clone();
|
let api = self.clone();
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
async move { api.0.maybe_package_info(&name).await }.boxed()
|
async move { api.0.maybe_package_info(&name).await }.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clear_memory_cache(&self) {
|
||||||
|
self.0.mem_cache.lock().clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RealNpmRegistryApiInner {
|
struct RealNpmRegistryApiInner {
|
||||||
base_url: Url,
|
base_url: Url,
|
||||||
cache: NpmCache,
|
cache: NpmCache,
|
||||||
mem_cache: Mutex<HashMap<String, Option<NpmPackageInfo>>>,
|
mem_cache: Mutex<HashMap<String, Option<Arc<NpmPackageInfo>>>>,
|
||||||
cache_setting: CacheSetting,
|
cache_setting: CacheSetting,
|
||||||
progress_bar: ProgressBar,
|
progress_bar: ProgressBar,
|
||||||
}
|
}
|
||||||
|
@ -287,7 +292,7 @@ impl RealNpmRegistryApiInner {
|
||||||
pub async fn maybe_package_info(
|
pub async fn maybe_package_info(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> Result<Option<NpmPackageInfo>, AnyError> {
|
) -> Result<Option<Arc<NpmPackageInfo>>, AnyError> {
|
||||||
let maybe_info = self.mem_cache.lock().get(name).cloned();
|
let maybe_info = self.mem_cache.lock().get(name).cloned();
|
||||||
if let Some(info) = maybe_info {
|
if let Some(info) = maybe_info {
|
||||||
Ok(info)
|
Ok(info)
|
||||||
|
@ -306,6 +311,7 @@ impl RealNpmRegistryApiInner {
|
||||||
format!("Error getting response at {}", self.get_package_url(name))
|
format!("Error getting response at {}", self.get_package_url(name))
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
let maybe_package_info = maybe_package_info.map(Arc::new);
|
||||||
|
|
||||||
// Not worth the complexity to ensure multiple in-flight requests
|
// Not worth the complexity to ensure multiple in-flight requests
|
||||||
// for the same package only request once because with how this is
|
// for the same package only request once because with how this is
|
||||||
|
@ -548,8 +554,12 @@ impl NpmRegistryApi for TestNpmRegistryApi {
|
||||||
fn maybe_package_info(
|
fn maybe_package_info(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> BoxFuture<'static, Result<Option<NpmPackageInfo>, AnyError>> {
|
) -> BoxFuture<'static, Result<Option<Arc<NpmPackageInfo>>, AnyError>> {
|
||||||
let result = self.package_infos.lock().get(name).cloned();
|
let result = self.package_infos.lock().get(name).cloned();
|
||||||
Box::pin(deno_core::futures::future::ready(Ok(result)))
|
Box::pin(deno_core::futures::future::ready(Ok(result.map(Arc::new))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_memory_cache(&self) {
|
||||||
|
// do nothing for the test api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -415,7 +415,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
version_matcher: &impl NpmVersionMatcher,
|
version_matcher: &impl NpmVersionMatcher,
|
||||||
package_info: NpmPackageInfo,
|
package_info: &NpmPackageInfo,
|
||||||
) -> Result<VersionAndInfo, AnyError> {
|
) -> Result<VersionAndInfo, AnyError> {
|
||||||
if let Some(version) =
|
if let Some(version) =
|
||||||
self.resolve_best_package_version(name, version_matcher)
|
self.resolve_best_package_version(name, version_matcher)
|
||||||
|
@ -462,10 +462,14 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
maybe_best_version.cloned()
|
maybe_best_version.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_package_req(&self, req: &NpmPackageReq) -> bool {
|
||||||
|
self.graph.has_package_req(req)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_package_req(
|
pub fn add_package_req(
|
||||||
&mut self,
|
&mut self,
|
||||||
package_req: &NpmPackageReq,
|
package_req: &NpmPackageReq,
|
||||||
package_info: NpmPackageInfo,
|
package_info: &NpmPackageInfo,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
let node = self.resolve_node_from_info(
|
let node = self.resolve_node_from_info(
|
||||||
&package_req.name,
|
&package_req.name,
|
||||||
|
@ -484,7 +488,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
fn analyze_dependency(
|
fn analyze_dependency(
|
||||||
&mut self,
|
&mut self,
|
||||||
entry: &NpmDependencyEntry,
|
entry: &NpmDependencyEntry,
|
||||||
package_info: NpmPackageInfo,
|
package_info: &NpmPackageInfo,
|
||||||
parent_id: &NpmPackageId,
|
parent_id: &NpmPackageId,
|
||||||
visited_versions: &Arc<VisitedVersionsPath>,
|
visited_versions: &Arc<VisitedVersionsPath>,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
@ -536,7 +540,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
name: &str,
|
||||||
version_matcher: &impl NpmVersionMatcher,
|
version_matcher: &impl NpmVersionMatcher,
|
||||||
package_info: NpmPackageInfo,
|
package_info: &NpmPackageInfo,
|
||||||
) -> Result<Arc<Mutex<Node>>, AnyError> {
|
) -> Result<Arc<Mutex<Node>>, AnyError> {
|
||||||
let version_and_info = self.resolve_best_package_version_and_info(
|
let version_and_info = self.resolve_best_package_version_and_info(
|
||||||
name,
|
name,
|
||||||
|
@ -613,7 +617,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
NpmDependencyEntryKind::Dep => {
|
NpmDependencyEntryKind::Dep => {
|
||||||
self.analyze_dependency(
|
self.analyze_dependency(
|
||||||
dep,
|
dep,
|
||||||
package_info,
|
&package_info,
|
||||||
&parent_id,
|
&parent_id,
|
||||||
&visited_versions,
|
&visited_versions,
|
||||||
)?;
|
)?;
|
||||||
|
@ -624,7 +628,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
&dep.bare_specifier,
|
&dep.bare_specifier,
|
||||||
&parent_id,
|
&parent_id,
|
||||||
dep,
|
dep,
|
||||||
package_info,
|
&package_info,
|
||||||
&visited_versions,
|
&visited_versions,
|
||||||
)?;
|
)?;
|
||||||
if let Some(new_parent_id) = maybe_new_parent_id {
|
if let Some(new_parent_id) = maybe_new_parent_id {
|
||||||
|
@ -647,7 +651,7 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi>
|
||||||
specifier: &str,
|
specifier: &str,
|
||||||
parent_id: &NpmPackageId,
|
parent_id: &NpmPackageId,
|
||||||
peer_dep: &NpmDependencyEntry,
|
peer_dep: &NpmDependencyEntry,
|
||||||
peer_package_info: NpmPackageInfo,
|
peer_package_info: &NpmPackageInfo,
|
||||||
visited_ancestor_versions: &Arc<VisitedVersionsPath>,
|
visited_ancestor_versions: &Arc<VisitedVersionsPath>,
|
||||||
) -> Result<Option<NpmPackageId>, AnyError> {
|
) -> Result<Option<NpmPackageId>, AnyError> {
|
||||||
fn find_matching_child<'a>(
|
fn find_matching_child<'a>(
|
||||||
|
@ -881,7 +885,7 @@ struct VersionAndInfo {
|
||||||
fn get_resolved_package_version_and_info(
|
fn get_resolved_package_version_and_info(
|
||||||
pkg_name: &str,
|
pkg_name: &str,
|
||||||
version_matcher: &impl NpmVersionMatcher,
|
version_matcher: &impl NpmVersionMatcher,
|
||||||
info: NpmPackageInfo,
|
info: &NpmPackageInfo,
|
||||||
parent: Option<&NpmPackageId>,
|
parent: Option<&NpmPackageId>,
|
||||||
) -> Result<VersionAndInfo, AnyError> {
|
) -> Result<VersionAndInfo, AnyError> {
|
||||||
let mut maybe_best_version: Option<VersionAndInfo> = None;
|
let mut maybe_best_version: Option<VersionAndInfo> = None;
|
||||||
|
@ -922,7 +926,7 @@ fn get_resolved_package_version_and_info(
|
||||||
bail!("Could not find dist-tag '{}'.", tag)
|
bail!("Could not find dist-tag '{}'.", tag)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (_, version_info) in info.versions.into_iter() {
|
for version_info in info.versions.values() {
|
||||||
let version = NpmVersion::parse(&version_info.version)?;
|
let version = NpmVersion::parse(&version_info.version)?;
|
||||||
if version_matcher.matches(&version) {
|
if version_matcher.matches(&version) {
|
||||||
let is_best_version = maybe_best_version
|
let is_best_version = maybe_best_version
|
||||||
|
@ -932,7 +936,7 @@ fn get_resolved_package_version_and_info(
|
||||||
if is_best_version {
|
if is_best_version {
|
||||||
maybe_best_version = Some(VersionAndInfo {
|
maybe_best_version = Some(VersionAndInfo {
|
||||||
version,
|
version,
|
||||||
info: version_info,
|
info: version_info.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -981,7 +985,7 @@ mod test {
|
||||||
let result = get_resolved_package_version_and_info(
|
let result = get_resolved_package_version_and_info(
|
||||||
"test",
|
"test",
|
||||||
&package_ref.req,
|
&package_ref.req,
|
||||||
NpmPackageInfo {
|
&NpmPackageInfo {
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
versions: HashMap::new(),
|
versions: HashMap::new(),
|
||||||
dist_tags: HashMap::from([(
|
dist_tags: HashMap::from([(
|
||||||
|
@ -1001,7 +1005,7 @@ mod test {
|
||||||
let result = get_resolved_package_version_and_info(
|
let result = get_resolved_package_version_and_info(
|
||||||
"test",
|
"test",
|
||||||
&package_ref.req,
|
&package_ref.req,
|
||||||
NpmPackageInfo {
|
&NpmPackageInfo {
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
versions: HashMap::from([
|
versions: HashMap::from([
|
||||||
("0.1.0".to_string(), NpmPackageVersionInfo::default()),
|
("0.1.0".to_string(), NpmPackageVersionInfo::default()),
|
||||||
|
@ -2014,7 +2018,7 @@ mod test {
|
||||||
for req in reqs {
|
for req in reqs {
|
||||||
let req = NpmPackageReference::from_str(req).unwrap().req;
|
let req = NpmPackageReference::from_str(req).unwrap().req;
|
||||||
resolver
|
resolver
|
||||||
.add_package_req(&req, api.package_info(&req.name).await.unwrap())
|
.add_package_req(&req, &api.package_info(&req.name).await.unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -422,42 +422,54 @@ impl NpmResolution {
|
||||||
// multiple packages are resolved in alphabetical order
|
// multiple packages are resolved in alphabetical order
|
||||||
package_reqs.sort_by(|a, b| a.name.cmp(&b.name));
|
package_reqs.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
|
||||||
// go over the top level packages first, then down the
|
// go over the top level package names first, then down the
|
||||||
// tree one level at a time through all the branches
|
// tree one level at a time through all the branches
|
||||||
let mut unresolved_tasks = Vec::with_capacity(package_reqs.len());
|
let mut unresolved_tasks = Vec::with_capacity(package_reqs.len());
|
||||||
for package_req in package_reqs {
|
let mut resolving_package_names =
|
||||||
if graph.has_package_req(&package_req) {
|
HashSet::with_capacity(package_reqs.len());
|
||||||
|
for package_req in &package_reqs {
|
||||||
|
if graph.has_package_req(package_req) {
|
||||||
// skip analyzing this package, as there's already a matching top level package
|
// skip analyzing this package, as there's already a matching top level package
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if !resolving_package_names.insert(package_req.name.clone()) {
|
||||||
|
continue; // already resolving
|
||||||
|
}
|
||||||
|
|
||||||
// no existing best version, so resolve the current packages
|
// cache the package info up front in parallel
|
||||||
let api = self.api.clone();
|
if should_sync_download() {
|
||||||
let maybe_info = if should_sync_download() {
|
|
||||||
// for deterministic test output
|
// for deterministic test output
|
||||||
Some(api.package_info(&package_req.name).await)
|
self.api.package_info(&package_req.name).await?;
|
||||||
} else {
|
} else {
|
||||||
None
|
let api = self.api.clone();
|
||||||
|
let package_name = package_req.name.clone();
|
||||||
|
unresolved_tasks.push(tokio::task::spawn(async move {
|
||||||
|
// This is ok to call because api will internally cache
|
||||||
|
// the package information in memory.
|
||||||
|
api.package_info(&package_name).await
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
unresolved_tasks.push(tokio::task::spawn(async move {
|
}
|
||||||
let info = match maybe_info {
|
|
||||||
Some(info) => info?,
|
for result in futures::future::join_all(unresolved_tasks).await {
|
||||||
None => api.package_info(&package_req.name).await?,
|
result??; // surface the first error
|
||||||
};
|
|
||||||
Result::<_, AnyError>::Ok((package_req, info))
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut resolver = GraphDependencyResolver::new(&mut graph, &self.api);
|
let mut resolver = GraphDependencyResolver::new(&mut graph, &self.api);
|
||||||
|
|
||||||
for result in futures::future::join_all(unresolved_tasks).await {
|
for package_req in package_reqs {
|
||||||
let (package_req, info) = result??;
|
// avoid loading the info if this is already in the graph
|
||||||
resolver.add_package_req(&package_req, info)?;
|
if !resolver.has_package_req(&package_req) {
|
||||||
|
let info = self.api.package_info(&package_req.name).await?;
|
||||||
|
resolver.add_package_req(&package_req, &info)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolver.resolve_pending().await?;
|
resolver.resolve_pending().await?;
|
||||||
|
|
||||||
graph.into_snapshot(&self.api).await
|
let result = graph.into_snapshot(&self.api).await;
|
||||||
|
self.api.clear_memory_cache();
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_package_from_id(
|
pub fn resolve_package_from_id(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
Download http://localhost:4545/subdir/mod2.ts
|
Download http://localhost:4545/subdir/mod2.ts
|
||||||
|
Download http://localhost:4545/subdir/mt_text_typescript.t1.ts
|
||||||
Download http://localhost:4545/subdir/print_hello.ts
|
Download http://localhost:4545/subdir/print_hello.ts
|
||||||
Check [WILDCARD]/fetch/test.ts
|
Check [WILDCARD]/fetch/test.ts
|
||||||
Download http://localhost:4545/subdir/mt_text_typescript.t1.ts
|
|
||||||
Check [WILDCARD]/fetch/other.ts
|
Check [WILDCARD]/fetch/other.ts
|
||||||
|
|
1
cli/tests/testdata/npm/deno_cache.out
vendored
1
cli/tests/testdata/npm/deno_cache.out
vendored
|
@ -1,5 +1,4 @@
|
||||||
Download http://localhost:4545/npm/registry/chalk
|
Download http://localhost:4545/npm/registry/chalk
|
||||||
Download http://localhost:4545/npm/registry/chalk/chalk-5.0.1.tgz
|
|
||||||
Download http://localhost:4545/npm/registry/mkdirp
|
Download http://localhost:4545/npm/registry/mkdirp
|
||||||
Download http://localhost:4545/npm/registry/chalk/chalk-5.0.1.tgz
|
Download http://localhost:4545/npm/registry/chalk/chalk-5.0.1.tgz
|
||||||
Download http://localhost:4545/npm/registry/mkdirp/mkdirp-1.0.4.tgz
|
Download http://localhost:4545/npm/registry/mkdirp/mkdirp-1.0.4.tgz
|
||||||
|
|
Loading…
Reference in a new issue